1 /** 2 * jsBezier 3 * 4 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 5 * 6 * licensed under the MIT license. 7 * 8 * a set of Bezier curve functions that deal with Beziers, used by jsPlumb, and perhaps useful for other people. These functions work with Bezier 9 * curves of arbitrary degree. 10 * 11 * - functions are all in the 'jsBezier' namespace. 12 * 13 * - all input points should be in the format {x:.., y:..}. all output points are in this format too. 14 * 15 * - all input curves should be in the format [ {x:.., y:..}, {x:.., y:..}, {x:.., y:..}, {x:.., y:..} ] 16 * 17 * - 'location' as used as an input here refers to a decimal in the range 0-1 inclusive, which indicates a point some proportion along the length 18 * of the curve. location as output has the same format and meaning. 19 * 20 * 21 * Function List: 22 * -------------- 23 * 24 * distanceFromCurve(point, curve) 25 * 26 * Calculates the distance that the given point lies from the given Bezier. Note that it is computed relative to the center of the Bezier, 27 * so if you have stroked the curve with a wide pen you may wish to take that into account! The distance returned is relative to the values 28 * of the curve and the point - it will most likely be pixels. 29 * 30 * gradientAtPoint(curve, location) 31 * 32 * Calculates the gradient to the curve at the given location, as a decimal between 0 and 1 inclusive. 33 * 34 * gradientAtPointAlongCurveFrom (curve, location) 35 * 36 * Calculates the gradient at the point on the given curve that is 'distance' units from location. 37 * 38 * nearestPointOnCurve(point, curve) 39 * 40 * Calculates the nearest point to the given point on the given curve. The return value of this is a JS object literal, containing both the 41 *point's coordinates and also the 'location' of the point (see above), for example: { point:{x:551,y:150}, location:0.263365 }. 42 * 43 * pointOnCurve(curve, location) 44 * 45 * Calculates the coordinates of the point on the given Bezier curve at the given location. 46 * 47 * pointAlongCurveFrom(curve, location, distance) 48 * 49 * Calculates the coordinates of the point on the given curve that is 'distance' units from location. 'distance' should be in the same coordinate 50 * space as that used to construct the Bezier curve. For an HTML Canvas usage, for example, distance would be a measure of pixels. 51 * 52 * locationAlongCurveFrom(curve, location, distance) 53 * 54 * Calculates the location on the given curve that is 'distance' units from location. 'distance' should be in the same coordinate 55 * space as that used to construct the Bezier curve. For an HTML Canvas usage, for example, distance would be a measure of pixels. 56 * 57 * perpendicularToCurveAt(curve, location, length, distance) 58 * 59 * Calculates the perpendicular to the given curve at the given location. length is the length of the line you wish for (it will be centered 60 * on the point at 'location'). distance is optional, and allows you to specify a point along the path from the given location as the center of 61 * the perpendicular returned. The return value of this is an array of two points: [ {x:...,y:...}, {x:...,y:...} ]. 62 * 63 * 64 */ 65 66 (function() { 67 68 var root = this; 69 70 if(typeof Math.sgn == "undefined") { 71 Math.sgn = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; }; 72 } 73 74 var Vectors = { 75 subtract : function(v1, v2) { return {x:v1.x - v2.x, y:v1.y - v2.y }; }, 76 dotProduct : function(v1, v2) { return (v1.x * v2.x) + (v1.y * v2.y); }, 77 square : function(v) { return Math.sqrt((v.x * v.x) + (v.y * v.y)); }, 78 scale : function(v, s) { return {x:v.x * s, y:v.y * s }; } 79 }, 80 81 maxRecursion = 64, 82 flatnessTolerance = Math.pow(2.0,-maxRecursion-1); 83 84 /** 85 * Calculates the distance that the point lies from the curve. 86 * 87 * @param point a point in the form {x:567, y:3342} 88 * @param curve a Bezier curve in the form [{x:..., y:...}, {x:..., y:...}, {x:..., y:...}, {x:..., y:...}]. note that this is currently 89 * hardcoded to assume cubiz beziers, but would be better off supporting any degree. 90 * @return a JS object literal containing location and distance, for example: {location:0.35, distance:10}. Location is analogous to the location 91 * argument you pass to the pointOnPath function: it is a ratio of distance travelled along the curve. Distance is the distance in pixels from 92 * the point to the curve. 93 */ 94 var _distanceFromCurve = function(point, curve) { 95 var candidates = [], 96 w = _convertToBezier(point, curve), 97 degree = curve.length - 1, higherDegree = (2 * degree) - 1, 98 numSolutions = _findRoots(w, higherDegree, candidates, 0), 99 v = Vectors.subtract(point, curve[0]), dist = Vectors.square(v), t = 0.0; 100 101 for (var i = 0; i < numSolutions; i++) { 102 v = Vectors.subtract(point, _bezier(curve, degree, candidates[i], null, null)); 103 var newDist = Vectors.square(v); 104 if (newDist < dist) { 105 dist = newDist; 106 t = candidates[i]; 107 } 108 } 109 v = Vectors.subtract(point, curve[degree]); 110 newDist = Vectors.square(v); 111 if (newDist < dist) { 112 dist = newDist; 113 t = 1.0; 114 } 115 return {location:t, distance:dist}; 116 }; 117 /** 118 * finds the nearest point on the curve to the given point. 119 */ 120 var _nearestPointOnCurve = function(point, curve) { 121 var td = _distanceFromCurve(point, curve); 122 return {point:_bezier(curve, curve.length - 1, td.location, null, null), location:td.location}; 123 }; 124 var _convertToBezier = function(point, curve) { 125 var degree = curve.length - 1, higherDegree = (2 * degree) - 1, 126 c = [], d = [], cdTable = [], w = [], 127 z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ]; 128 129 for (var i = 0; i <= degree; i++) c[i] = Vectors.subtract(curve[i], point); 130 for (var i = 0; i <= degree - 1; i++) { 131 d[i] = Vectors.subtract(curve[i+1], curve[i]); 132 d[i] = Vectors.scale(d[i], 3.0); 133 } 134 for (var row = 0; row <= degree - 1; row++) { 135 for (var column = 0; column <= degree; column++) { 136 if (!cdTable[row]) cdTable[row] = []; 137 cdTable[row][column] = Vectors.dotProduct(d[row], c[column]); 138 } 139 } 140 for (i = 0; i <= higherDegree; i++) { 141 if (!w[i]) w[i] = []; 142 w[i].y = 0.0; 143 w[i].x = parseFloat(i) / higherDegree; 144 } 145 var n = degree, m = degree-1; 146 for (var k = 0; k <= n + m; k++) { 147 var lb = Math.max(0, k - m), 148 ub = Math.min(k, n); 149 for (i = lb; i <= ub; i++) { 150 j = k - i; 151 w[i+j].y += cdTable[j][i] * z[j][i]; 152 } 153 } 154 return w; 155 }; 156 /** 157 * counts how many roots there are. 158 */ 159 var _findRoots = function(w, degree, t, depth) { 160 var left = [], right = [], 161 left_count, right_count, 162 left_t = [], right_t = []; 163 164 switch (_getCrossingCount(w, degree)) { 165 case 0 : { 166 return 0; 167 } 168 case 1 : { 169 if (depth >= maxRecursion) { 170 t[0] = (w[0].x + w[degree].x) / 2.0; 171 return 1; 172 } 173 if (_isFlatEnough(w, degree)) { 174 t[0] = _computeXIntercept(w, degree); 175 return 1; 176 } 177 break; 178 } 179 } 180 _bezier(w, degree, 0.5, left, right); 181 left_count = _findRoots(left, degree, left_t, depth+1); 182 right_count = _findRoots(right, degree, right_t, depth+1); 183 for (var i = 0; i < left_count; i++) t[i] = left_t[i]; 184 for (var i = 0; i < right_count; i++) t[i+left_count] = right_t[i]; 185 return (left_count+right_count); 186 }; 187 var _getCrossingCount = function(curve, degree) { 188 var n_crossings = 0, sign, old_sign; 189 sign = old_sign = Math.sgn(curve[0].y); 190 for (var i = 1; i <= degree; i++) { 191 sign = Math.sgn(curve[i].y); 192 if (sign != old_sign) n_crossings++; 193 old_sign = sign; 194 } 195 return n_crossings; 196 }; 197 var _isFlatEnough = function(curve, degree) { 198 var error, 199 intercept_1, intercept_2, left_intercept, right_intercept, 200 a, b, c, det, dInv, a1, b1, c1, a2, b2, c2; 201 a = curve[0].y - curve[degree].y; 202 b = curve[degree].x - curve[0].x; 203 c = curve[0].x * curve[degree].y - curve[degree].x * curve[0].y; 204 205 var max_distance_above = max_distance_below = 0.0; 206 207 for (var i = 1; i < degree; i++) { 208 var value = a * curve[i].x + b * curve[i].y + c; 209 if (value > max_distance_above) 210 max_distance_above = value; 211 else if (value < max_distance_below) 212 max_distance_below = value; 213 } 214 215 a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b; 216 c2 = c - max_distance_above; 217 det = a1 * b2 - a2 * b1; 218 dInv = 1.0/det; 219 intercept_1 = (b1 * c2 - b2 * c1) * dInv; 220 a2 = a; b2 = b; c2 = c - max_distance_below; 221 det = a1 * b2 - a2 * b1; 222 dInv = 1.0/det; 223 intercept_2 = (b1 * c2 - b2 * c1) * dInv; 224 left_intercept = Math.min(intercept_1, intercept_2); 225 right_intercept = Math.max(intercept_1, intercept_2); 226 error = right_intercept - left_intercept; 227 return (error < flatnessTolerance)? 1 : 0; 228 }; 229 var _computeXIntercept = function(curve, degree) { 230 var XLK = 1.0, YLK = 0.0, 231 XNM = curve[degree].x - curve[0].x, YNM = curve[degree].y - curve[0].y, 232 XMK = curve[0].x - 0.0, YMK = curve[0].y - 0.0, 233 det = XNM*YLK - YNM*XLK, detInv = 1.0/det, 234 S = (XNM*YMK - YNM*XMK) * detInv; 235 return 0.0 + XLK * S; 236 }; 237 var _bezier = function(curve, degree, t, left, right) { 238 var temp = [[]]; 239 for (var j =0; j <= degree; j++) temp[0][j] = curve[j]; 240 for (var i = 1; i <= degree; i++) { 241 for (var j =0 ; j <= degree - i; j++) { 242 if (!temp[i]) temp[i] = []; 243 if (!temp[i][j]) temp[i][j] = {}; 244 temp[i][j].x = (1.0 - t) * temp[i-1][j].x + t * temp[i-1][j+1].x; 245 temp[i][j].y = (1.0 - t) * temp[i-1][j].y + t * temp[i-1][j+1].y; 246 } 247 } 248 if (left != null) 249 for (j = 0; j <= degree; j++) left[j] = temp[j][0]; 250 if (right != null) 251 for (j = 0; j <= degree; j++) right[j] = temp[degree-j][j]; 252 253 return (temp[degree][0]); 254 }; 255 256 var _curveFunctionCache = {}; 257 var _getCurveFunctions = function(order) { 258 var fns = _curveFunctionCache[order]; 259 if (!fns) { 260 fns = []; 261 var f_term = function() { return function(t) { return Math.pow(t, order); }; }, 262 l_term = function() { return function(t) { return Math.pow((1-t), order); }; }, 263 c_term = function(c) { return function(t) { return c; }; }, 264 t_term = function() { return function(t) { return t; }; }, 265 one_minus_t_term = function() { return function(t) { return 1-t; }; }, 266 _termFunc = function(terms) { 267 return function(t) { 268 var p = 1; 269 for (var i = 0; i < terms.length; i++) p = p * terms[i](t); 270 return p; 271 }; 272 }; 273 274 fns.push(new f_term()); // first is t to the power of the curve order 275 for (var i = 1; i < order; i++) { 276 var terms = [new c_term(order)]; 277 for (var j = 0 ; j < (order - i); j++) terms.push(new t_term()); 278 for (var j = 0 ; j < i; j++) terms.push(new one_minus_t_term()); 279 fns.push(new _termFunc(terms)); 280 } 281 fns.push(new l_term()); // last is (1-t) to the power of the curve order 282 283 _curveFunctionCache[order] = fns; 284 } 285 286 return fns; 287 }; 288 289 290 /** 291 * calculates a point on the curve, for a Bezier of arbitrary order. 292 * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}, {x:120,y:100}]. For a cubic bezier this should have four points. 293 * @param location a decimal indicating the distance along the curve the point should be located at. this is the distance along the curve as it travels, taking the way it bends into account. should be a number from 0 to 1, inclusive. 294 */ 295 var _pointOnPath = function(curve, location) { 296 var cc = _getCurveFunctions(curve.length - 1), 297 _x = 0, _y = 0; 298 for (var i = 0; i < curve.length ; i++) { 299 _x = _x + (curve[i].x * cc[i](location)); 300 _y = _y + (curve[i].y * cc[i](location)); 301 } 302 303 return {x:_x, y:_y}; 304 }; 305 306 var _dist = function(p1,p2) { 307 return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); 308 }; 309 310 var _isPoint = function(curve) { 311 return curve[0].x == curve[1].x && curve[0].y == curve[1].y; 312 }; 313 314 /** 315 * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also 316 * its 'location' (proportion of travel along the path); the method below - _pointAlongPathFrom - calls this method and just returns the 317 * point. 318 */ 319 var _pointAlongPath = function(curve, location, distance) { 320 321 if (_isPoint(curve)) { 322 return { 323 point:curve[0], 324 location:location 325 }; 326 } 327 328 var prev = _pointOnPath(curve, location), 329 tally = 0, 330 curLoc = location, 331 direction = distance > 0 ? 1 : -1, 332 cur = null; 333 334 while (tally < Math.abs(distance)) { 335 curLoc += (0.005 * direction); 336 cur = _pointOnPath(curve, curLoc); 337 tally += _dist(cur, prev); 338 prev = cur; 339 } 340 return {point:cur, location:curLoc}; 341 }; 342 343 var _length = function(curve) { 344 if (_isPoint(curve)) return 0; 345 346 var prev = _pointOnPath(curve, 0), 347 tally = 0, 348 curLoc = 0, 349 direction = 1, 350 cur = null; 351 352 while (curLoc < 1) { 353 curLoc += (0.005 * direction); 354 cur = _pointOnPath(curve, curLoc); 355 tally += _dist(cur, prev); 356 prev = cur; 357 } 358 return tally; 359 }; 360 361 /** 362 * finds the point that is 'distance' along the path from 'location'. 363 */ 364 var _pointAlongPathFrom = function(curve, location, distance) { 365 return _pointAlongPath(curve, location, distance).point; 366 }; 367 368 /** 369 * finds the location that is 'distance' along the path from 'location'. 370 */ 371 var _locationAlongPathFrom = function(curve, location, distance) { 372 return _pointAlongPath(curve, location, distance).location; 373 }; 374 375 /** 376 * returns the gradient of the curve at the given location, which is a decimal between 0 and 1 inclusive. 377 * 378 * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html 379 */ 380 var _gradientAtPoint = function(curve, location) { 381 var p1 = _pointOnPath(curve, location), 382 p2 = _pointOnPath(curve.slice(0, curve.length - 1), location), 383 dy = p2.y - p1.y, dx = p2.x - p1.x; 384 return dy == 0 ? Infinity : Math.atan(dy / dx); 385 }; 386 387 /** 388 returns the gradient of the curve at the point which is 'distance' from the given location. 389 if this point is greater than location 1, the gradient at location 1 is returned. 390 if this point is less than location 0, the gradient at location 0 is returned. 391 */ 392 var _gradientAtPointAlongPathFrom = function(curve, location, distance) { 393 var p = _pointAlongPath(curve, location, distance); 394 if (p.location > 1) p.location = 1; 395 if (p.location < 0) p.location = 0; 396 return _gradientAtPoint(curve, p.location); 397 }; 398 399 /** 400 * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location. 401 * if distance is not supplied, the perpendicular for the given location is computed (ie. we set distance to zero). 402 */ 403 var _perpendicularToPathAt = function(curve, location, length, distance) { 404 distance = distance == null ? 0 : distance; 405 var p = _pointAlongPath(curve, location, distance), 406 m = _gradientAtPoint(curve, p.location), 407 _theta2 = Math.atan(-1 / m), 408 y = length / 2 * Math.sin(_theta2), 409 x = length / 2 * Math.cos(_theta2); 410 return [{x:p.point.x + x, y:p.point.y + y}, {x:p.point.x - x, y:p.point.y - y}]; 411 }; 412 413 var jsBezier = this.jsBezier = { 414 distanceFromCurve : _distanceFromCurve, 415 gradientAtPoint : _gradientAtPoint, 416 gradientAtPointAlongCurveFrom : _gradientAtPointAlongPathFrom, 417 nearestPointOnCurve : _nearestPointOnCurve, 418 pointOnCurve : _pointOnPath, 419 pointAlongCurveFrom : _pointAlongPathFrom, 420 perpendicularToCurveAt : _perpendicularToPathAt, 421 locationAlongCurveFrom:_locationAlongPathFrom, 422 getLength:_length, 423 version:"0.9.0" 424 }; 425 426 if (typeof exports !== "undefined") { 427 exports.jsBezier = jsBezier; 428 } 429 430 }).call(typeof window !== 'undefined' ? window : this); 431 432 /** 433 * Biltong v0.4.0 434 * 435 * Various geometry functions written as part of jsPlumb and perhaps useful for others. 436 * 437 * Copyright (c) 2017 jsPlumb 438 * https://jsplumbtoolkit.com 439 * 440 * Permission is hereby granted, free of charge, to any person 441 * obtaining a copy of this software and associated documentation 442 * files (the "Software"), to deal in the Software without 443 * restriction, including without limitation the rights to use, 444 * copy, modify, merge, publish, distribute, sublicense, and/or sell 445 * copies of the Software, and to permit persons to whom the 446 * Software is furnished to do so, subject to the following 447 * conditions: 448 * 449 * The above copyright notice and this permission notice shall be 450 * included in all copies or substantial portions of the Software. 451 * 452 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 453 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 454 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 455 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 456 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 457 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 458 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 459 * OTHER DEALINGS IN THE SOFTWARE. 460 */ 461 ;(function() { 462 463 "use strict"; 464 var root = this; 465 466 var Biltong = root.Biltong = { 467 version:"0.4.0" 468 }; 469 470 if (typeof exports !== "undefined") { 471 exports.Biltong = Biltong; 472 } 473 474 var _isa = function(a) { return Object.prototype.toString.call(a) === "[object Array]"; }, 475 _pointHelper = function(p1, p2, fn) { 476 p1 = _isa(p1) ? p1 : [p1.x, p1.y]; 477 p2 = _isa(p2) ? p2 : [p2.x, p2.y]; 478 return fn(p1, p2); 479 }, 480 /** 481 * @name Biltong.gradient 482 * @function 483 * @desc Calculates the gradient of a line between the two points. 484 * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties. 485 * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties. 486 * @return {Float} The gradient of a line between the two points. 487 */ 488 _gradient = Biltong.gradient = function(p1, p2) { 489 return _pointHelper(p1, p2, function(_p1, _p2) { 490 if (_p2[0] == _p1[0]) 491 return _p2[1] > _p1[1] ? Infinity : -Infinity; 492 else if (_p2[1] == _p1[1]) 493 return _p2[0] > _p1[0] ? 0 : -0; 494 else 495 return (_p2[1] - _p1[1]) / (_p2[0] - _p1[0]); 496 }); 497 }, 498 /** 499 * @name Biltong.normal 500 * @function 501 * @desc Calculates the gradient of a normal to a line between the two points. 502 * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties. 503 * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties. 504 * @return {Float} The gradient of a normal to a line between the two points. 505 */ 506 _normal = Biltong.normal = function(p1, p2) { 507 return -1 / _gradient(p1, p2); 508 }, 509 /** 510 * @name Biltong.lineLength 511 * @function 512 * @desc Calculates the length of a line between the two points. 513 * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties. 514 * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties. 515 * @return {Float} The length of a line between the two points. 516 */ 517 _lineLength = Biltong.lineLength = function(p1, p2) { 518 return _pointHelper(p1, p2, function(_p1, _p2) { 519 return Math.sqrt(Math.pow(_p2[1] - _p1[1], 2) + Math.pow(_p2[0] - _p1[0], 2)); 520 }); 521 }, 522 /** 523 * @name Biltong.quadrant 524 * @function 525 * @desc Calculates the quadrant in which the angle between the two points lies. 526 * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties. 527 * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties. 528 * @return {Integer} The quadrant - 1 for upper right, 2 for lower right, 3 for lower left, 4 for upper left. 529 */ 530 _quadrant = Biltong.quadrant = function(p1, p2) { 531 return _pointHelper(p1, p2, function(_p1, _p2) { 532 if (_p2[0] > _p1[0]) { 533 return (_p2[1] > _p1[1]) ? 2 : 1; 534 } 535 else if (_p2[0] == _p1[0]) { 536 return _p2[1] > _p1[1] ? 2 : 1; 537 } 538 else { 539 return (_p2[1] > _p1[1]) ? 3 : 4; 540 } 541 }); 542 }, 543 /** 544 * @name Biltong.theta 545 * @function 546 * @desc Calculates the angle between the two points. 547 * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties. 548 * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties. 549 * @return {Float} The angle between the two points. 550 */ 551 _theta = Biltong.theta = function(p1, p2) { 552 return _pointHelper(p1, p2, function(_p1, _p2) { 553 var m = _gradient(_p1, _p2), 554 t = Math.atan(m), 555 s = _quadrant(_p1, _p2); 556 if ((s == 4 || s== 3)) t += Math.PI; 557 if (t < 0) t += (2 * Math.PI); 558 559 return t; 560 }); 561 }, 562 /** 563 * @name Biltong.intersects 564 * @function 565 * @desc Calculates whether or not the two rectangles intersect. 566 * @param {Rectangle} r1 First rectangle, as a js object in the form `{x:.., y:.., w:.., h:..}` 567 * @param {Rectangle} r2 Second rectangle, as a js object in the form `{x:.., y:.., w:.., h:..}` 568 * @return {Boolean} True if the rectangles intersect, false otherwise. 569 */ 570 _intersects = Biltong.intersects = function(r1, r2) { 571 var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, 572 a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h; 573 574 return ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) || 575 ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) || 576 ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || 577 ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || 578 ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) || 579 ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) || 580 ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) || 581 ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ); 582 }, 583 /** 584 * @name Biltong.encloses 585 * @function 586 * @desc Calculates whether or not r2 is completely enclosed by r1. 587 * @param {Rectangle} r1 First rectangle, as a js object in the form `{x:.., y:.., w:.., h:..}` 588 * @param {Rectangle} r2 Second rectangle, as a js object in the form `{x:.., y:.., w:.., h:..}` 589 * @param {Boolean} [allowSharedEdges=false] If true, the concept of enclosure allows for one or more edges to be shared by the two rectangles. 590 * @return {Boolean} True if r1 encloses r2, false otherwise. 591 */ 592 _encloses = Biltong.encloses = function(r1, r2, allowSharedEdges) { 593 var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, 594 a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h, 595 c = function(v1, v2, v3, v4) { return allowSharedEdges ? v1 <= v2 && v3>= v4 : v1 < v2 && v3 > v4; }; 596 597 return c(x1,a1,x2,a2) && c(y1,b1,y2,b2); 598 }, 599 _segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], 600 _inverseSegmentMultipliers = [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], 601 /** 602 * @name Biltong.pointOnLine 603 * @function 604 * @desc Calculates a point on the line from `fromPoint` to `toPoint` that is `distance` units along the length of the line. 605 * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties. 606 * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties. 607 * @return {Point} Point on the line, in the form `{ x:..., y:... }`. 608 */ 609 _pointOnLine = Biltong.pointOnLine = function(fromPoint, toPoint, distance) { 610 var m = _gradient(fromPoint, toPoint), 611 s = _quadrant(fromPoint, toPoint), 612 segmentMultiplier = distance > 0 ? _segmentMultipliers[s] : _inverseSegmentMultipliers[s], 613 theta = Math.atan(m), 614 y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], 615 x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; 616 return { x:fromPoint.x + x, y:fromPoint.y + y }; 617 }, 618 /** 619 * @name Biltong.perpendicularLineTo 620 * @function 621 * @desc Calculates a line of length `length` that is perpendicular to the line from `fromPoint` to `toPoint` and passes through `toPoint`. 622 * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties. 623 * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties. 624 * @return {Line} Perpendicular line, in the form `[ { x:..., y:... }, { x:..., y:... } ]`. 625 */ 626 _perpendicularLineTo = Biltong.perpendicularLineTo = function(fromPoint, toPoint, length) { 627 var m = _gradient(fromPoint, toPoint), 628 theta2 = Math.atan(-1 / m), 629 y = length / 2 * Math.sin(theta2), 630 x = length / 2 * Math.cos(theta2); 631 return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; 632 }; 633 }).call(typeof window !== 'undefined' ? window : this); 634 ; 635 (function () { 636 637 "use strict"; 638 639 var root = this, 640 Sniff = { 641 android: navigator.userAgent.toLowerCase().indexOf("android") > -1 642 }, 643 matchesSelector = function (el, selector, ctx) { 644 ctx = ctx || el.parentNode; 645 var possibles = ctx.querySelectorAll(selector); 646 for (var i = 0; i < possibles.length; i++) { 647 if (possibles[i] === el) { 648 return true; 649 } 650 } 651 return false; 652 }, 653 _gel = function (el) { 654 return (typeof el == "string" || el.constructor === String) ? document.getElementById(el) : el; 655 }, 656 _t = function (e) { 657 return e.srcElement || e.target; 658 }, 659 // 660 // gets path info for the given event - the path from target to obj, in the event's bubble chain. if doCompute 661 // is false we just return target for the path. 662 // 663 _pi = function(e, target, obj, doCompute) { 664 if (!doCompute) return { path:[target], end:1 }; 665 else if (typeof e.path !== "undefined" && e.path.indexOf) { 666 return { path: e.path, end: e.path.indexOf(obj) }; 667 } else { 668 var out = { path:[], end:-1 }, _one = function(el) { 669 out.path.push(el); 670 if (el === obj) { 671 out.end = out.path.length - 1; 672 } 673 else if (el.parentNode != null) { 674 _one(el.parentNode) 675 } 676 }; 677 _one(target); 678 return out; 679 } 680 }, 681 _d = function (l, fn) { 682 for (var i = 0, j = l.length; i < j; i++) { 683 if (l[i] == fn) break; 684 } 685 if (i < l.length) l.splice(i, 1); 686 }, 687 guid = 1, 688 // 689 // this function generates a guid for every handler, sets it on the handler, then adds 690 // it to the associated object's map of handlers for the given event. this is what enables us 691 // to unbind all events of some type, or all events (the second of which can be requested by the user, 692 // but it also used by Mottle when an element is removed.) 693 _store = function (obj, event, fn) { 694 var g = guid++; 695 obj.__ta = obj.__ta || {}; 696 obj.__ta[event] = obj.__ta[event] || {}; 697 // store each handler with a unique guid. 698 obj.__ta[event][g] = fn; 699 // set the guid on the handler. 700 fn.__tauid = g; 701 return g; 702 }, 703 _unstore = function (obj, event, fn) { 704 obj.__ta && obj.__ta[event] && delete obj.__ta[event][fn.__tauid]; 705 // a handler might have attached extra functions, so we unbind those too. 706 if (fn.__taExtra) { 707 for (var i = 0; i < fn.__taExtra.length; i++) { 708 _unbind(obj, fn.__taExtra[i][0], fn.__taExtra[i][1]); 709 } 710 fn.__taExtra.length = 0; 711 } 712 // a handler might have attached an unstore callback 713 fn.__taUnstore && fn.__taUnstore(); 714 }, 715 _curryChildFilter = function (children, obj, fn, evt) { 716 if (children == null) return fn; 717 else { 718 var c = children.split(","), 719 _fn = function (e) { 720 _fn.__tauid = fn.__tauid; 721 var t = _t(e), target = t; // t is the target element on which the event occurred. it is the 722 // element we will wish to pass to any callbacks. 723 var pathInfo = _pi(e, t, obj, children != null) 724 if (pathInfo.end != -1) { 725 for (var p = 0; p < pathInfo.end; p++) { 726 target = pathInfo.path[p]; 727 for (var i = 0; i < c.length; i++) { 728 if (matchesSelector(target, c[i], obj)) { 729 fn.apply(target, arguments); 730 } 731 } 732 } 733 } 734 }; 735 registerExtraFunction(fn, evt, _fn); 736 return _fn; 737 } 738 }, 739 // 740 // registers an 'extra' function on some event listener function we were given - a function that we 741 // created and bound to the element as part of our housekeeping, and which we want to unbind and remove 742 // whenever the given function is unbound. 743 registerExtraFunction = function (fn, evt, newFn) { 744 fn.__taExtra = fn.__taExtra || []; 745 fn.__taExtra.push([evt, newFn]); 746 }, 747 DefaultHandler = function (obj, evt, fn, children) { 748 if (isTouchDevice && touchMap[evt]) { 749 var tfn = _curryChildFilter(children, obj, fn, touchMap[evt]); 750 _bind(obj, touchMap[evt], tfn , fn); 751 } 752 if (evt === "focus" && obj.getAttribute("tabindex") == null) { 753 obj.setAttribute("tabindex", "1"); 754 } 755 _bind(obj, evt, _curryChildFilter(children, obj, fn, evt), fn); 756 }, 757 SmartClickHandler = function (obj, evt, fn, children) { 758 if (obj.__taSmartClicks == null) { 759 var down = function (e) { 760 obj.__tad = _pageLocation(e); 761 }, 762 up = function (e) { 763 obj.__tau = _pageLocation(e); 764 }, 765 click = function (e) { 766 if (obj.__tad && obj.__tau && obj.__tad[0] === obj.__tau[0] && obj.__tad[1] === obj.__tau[1]) { 767 for (var i = 0; i < obj.__taSmartClicks.length; i++) 768 obj.__taSmartClicks[i].apply(_t(e), [ e ]); 769 } 770 }; 771 DefaultHandler(obj, "mousedown", down, children); 772 DefaultHandler(obj, "mouseup", up, children); 773 DefaultHandler(obj, "click", click, children); 774 obj.__taSmartClicks = []; 775 } 776 777 // store in the list of callbacks 778 obj.__taSmartClicks.push(fn); 779 // the unstore function removes this function from the object's listener list for this type. 780 fn.__taUnstore = function () { 781 _d(obj.__taSmartClicks, fn); 782 }; 783 }, 784 _tapProfiles = { 785 "tap": {touches: 1, taps: 1}, 786 "dbltap": {touches: 1, taps: 2}, 787 "contextmenu": {touches: 2, taps: 1} 788 }, 789 TapHandler = function (clickThreshold, dblClickThreshold) { 790 return function (obj, evt, fn, children) { 791 // if event is contextmenu, for devices which are mouse only, we want to 792 // use the default bind. 793 if (evt == "contextmenu" && isMouseDevice) 794 DefaultHandler(obj, evt, fn, children); 795 else { 796 // the issue here is that this down handler gets registered only for the 797 // child nodes in the first registration. in fact it should be registered with 798 // no child selector and then on down we should cycle through the registered 799 // functions to see if one of them matches. on mouseup we should execute ALL of 800 // the functions whose children are either null or match the element. 801 if (obj.__taTapHandler == null) { 802 var tt = obj.__taTapHandler = { 803 tap: [], 804 dbltap: [], 805 contextmenu: [], 806 down: false, 807 taps: 0, 808 downSelectors: [] 809 }; 810 var down = function (e) { 811 var target = _t(e), pathInfo = _pi(e, target, obj, children != null), finished = false; 812 for (var p = 0; p < pathInfo.end; p++) { 813 if (finished) return; 814 target = pathInfo.path[p]; 815 for (var i = 0; i < tt.downSelectors.length; i++) { 816 if (tt.downSelectors[i] == null || matchesSelector(target, tt.downSelectors[i], obj)) { 817 tt.down = true; 818 setTimeout(clearSingle, clickThreshold); 819 setTimeout(clearDouble, dblClickThreshold); 820 finished = true; 821 break; // we only need one match on mousedown 822 } 823 } 824 } 825 }, 826 up = function (e) { 827 if (tt.down) { 828 var target = _t(e), currentTarget, pathInfo; 829 tt.taps++; 830 var tc = _touchCount(e); 831 for (var eventId in _tapProfiles) { 832 if (_tapProfiles.hasOwnProperty(eventId)) { 833 var p = _tapProfiles[eventId]; 834 if (p.touches === tc && (p.taps === 1 || p.taps === tt.taps)) { 835 for (var i = 0; i < tt[eventId].length; i++) { 836 pathInfo = _pi(e, target, obj, tt[eventId][i][1] != null); 837 for (var pLoop = 0; pLoop < pathInfo.end; pLoop++) { 838 currentTarget = pathInfo.path[pLoop]; 839 // this is a single event registration handler. 840 if (tt[eventId][i][1] == null || matchesSelector(currentTarget, tt[eventId][i][1], obj)) { 841 tt[eventId][i][0].apply(currentTarget, [ e ]); 842 break; 843 } 844 } 845 } 846 } 847 } 848 } 849 } 850 }, 851 clearSingle = function () { 852 tt.down = false; 853 }, 854 clearDouble = function () { 855 tt.taps = 0; 856 }; 857 858 DefaultHandler(obj, "mousedown", down); 859 DefaultHandler(obj, "mouseup", up); 860 } 861 // add this child selector (it can be null, that's fine). 862 obj.__taTapHandler.downSelectors.push(children); 863 864 obj.__taTapHandler[evt].push([fn, children]); 865 // the unstore function removes this function from the object's listener list for this type. 866 fn.__taUnstore = function () { 867 _d(obj.__taTapHandler[evt], fn); 868 }; 869 } 870 }; 871 }, 872 meeHelper = function (type, evt, obj, target) { 873 for (var i in obj.__tamee[type]) { 874 if (obj.__tamee[type].hasOwnProperty(i)) { 875 obj.__tamee[type][i].apply(target, [ evt ]); 876 } 877 } 878 }, 879 MouseEnterExitHandler = function () { 880 var activeElements = []; 881 return function (obj, evt, fn, children) { 882 if (!obj.__tamee) { 883 // __tamee holds a flag saying whether the mouse is currently "in" the element, and a list of 884 // both mouseenter and mouseexit functions. 885 obj.__tamee = { over: false, mouseenter: [], mouseexit: [] }; 886 // register over and out functions 887 var over = function (e) { 888 var t = _t(e); 889 if ((children == null && (t == obj && !obj.__tamee.over)) || (matchesSelector(t, children, obj) && (t.__tamee == null || !t.__tamee.over))) { 890 meeHelper("mouseenter", e, obj, t); 891 t.__tamee = t.__tamee || {}; 892 t.__tamee.over = true; 893 activeElements.push(t); 894 } 895 }, 896 out = function (e) { 897 var t = _t(e); 898 // is the current target one of the activeElements? and is the 899 // related target NOT a descendant of it? 900 for (var i = 0; i < activeElements.length; i++) { 901 if (t == activeElements[i] && !matchesSelector((e.relatedTarget || e.toElement), "*", t)) { 902 t.__tamee.over = false; 903 activeElements.splice(i, 1); 904 meeHelper("mouseexit", e, obj, t); 905 } 906 } 907 }; 908 909 _bind(obj, "mouseover", _curryChildFilter(children, obj, over, "mouseover"), over); 910 _bind(obj, "mouseout", _curryChildFilter(children, obj, out, "mouseout"), out); 911 } 912 913 fn.__taUnstore = function () { 914 delete obj.__tamee[evt][fn.__tauid]; 915 }; 916 917 _store(obj, evt, fn); 918 obj.__tamee[evt][fn.__tauid] = fn; 919 }; 920 }, 921 isTouchDevice = "ontouchstart" in document.documentElement, 922 isMouseDevice = "onmousedown" in document.documentElement, 923 touchMap = { "mousedown": "touchstart", "mouseup": "touchend", "mousemove": "touchmove" }, 924 touchstart = "touchstart", touchend = "touchend", touchmove = "touchmove", 925 iev = (function () { 926 var rv = -1; 927 if (navigator.appName == 'Microsoft Internet Explorer') { 928 var ua = navigator.userAgent, 929 re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); 930 if (re.exec(ua) != null) 931 rv = parseFloat(RegExp.$1); 932 } 933 return rv; 934 })(), 935 isIELT9 = iev > -1 && iev < 9, 936 _genLoc = function (e, prefix) { 937 if (e == null) return [ 0, 0 ]; 938 var ts = _touches(e), t = _getTouch(ts, 0); 939 return [t[prefix + "X"], t[prefix + "Y"]]; 940 }, 941 _pageLocation = function (e) { 942 if (e == null) return [ 0, 0 ]; 943 if (isIELT9) { 944 return [ e.clientX + document.documentElement.scrollLeft, e.clientY + document.documentElement.scrollTop ]; 945 } 946 else { 947 return _genLoc(e, "page"); 948 } 949 }, 950 _screenLocation = function (e) { 951 return _genLoc(e, "screen"); 952 }, 953 _clientLocation = function (e) { 954 return _genLoc(e, "client"); 955 }, 956 _getTouch = function (touches, idx) { 957 return touches.item ? touches.item(idx) : touches[idx]; 958 }, 959 _touches = function (e) { 960 return e.touches && e.touches.length > 0 ? e.touches : 961 e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches : 962 e.targetTouches && e.targetTouches.length > 0 ? e.targetTouches : 963 [ e ]; 964 }, 965 _touchCount = function (e) { 966 return _touches(e).length; 967 }, 968 //http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html 969 _bind = function (obj, type, fn, originalFn) { 970 _store(obj, type, fn); 971 originalFn.__tauid = fn.__tauid; 972 if (obj.addEventListener) 973 obj.addEventListener(type, fn, false); 974 else if (obj.attachEvent) { 975 var key = type + fn.__tauid; 976 obj["e" + key] = fn; 977 // TODO look at replacing with .call(..) 978 obj[key] = function () { 979 obj["e" + key] && obj["e" + key](window.event); 980 }; 981 obj.attachEvent("on" + type, obj[key]); 982 } 983 }, 984 _unbind = function (obj, type, fn) { 985 if (fn == null) return; 986 _each(obj, function () { 987 var _el = _gel(this); 988 _unstore(_el, type, fn); 989 // it has been bound if there is a tauid. otherwise it was not bound and we can ignore it. 990 if (fn.__tauid != null) { 991 if (_el.removeEventListener) { 992 _el.removeEventListener(type, fn, false); 993 if (isTouchDevice && touchMap[type]) _el.removeEventListener(touchMap[type], fn, false); 994 } 995 else if (this.detachEvent) { 996 var key = type + fn.__tauid; 997 _el[key] && _el.detachEvent("on" + type, _el[key]); 998 _el[key] = null; 999 _el["e" + key] = null; 1000 } 1001 } 1002 1003 // if a touch event was also registered, deregister now. 1004 if (fn.__taTouchProxy) { 1005 _unbind(obj, fn.__taTouchProxy[1], fn.__taTouchProxy[0]); 1006 } 1007 }); 1008 }, 1009 _each = function (obj, fn) { 1010 if (obj == null) return; 1011 // if a list (or list-like), use it. if a string, get a list 1012 // by running the string through querySelectorAll. else, assume 1013 // it's an Element. 1014 // obj.top is "unknown" in IE8. 1015 obj = (typeof Window !== "undefined" && (typeof obj.top !== "unknown" && obj == obj.top)) ? [ obj ] : 1016 (typeof obj !== "string") && (obj.tagName == null && obj.length != null) ? obj : 1017 typeof obj === "string" ? document.querySelectorAll(obj) 1018 : [ obj ]; 1019 1020 for (var i = 0; i < obj.length; i++) 1021 fn.apply(obj[i]); 1022 }; 1023 1024 /** 1025 * Mottle offers support for abstracting out the differences 1026 * between touch and mouse devices, plus "smart click" functionality 1027 * (don't fire click if the mouse has moved between mousedown and mouseup), 1028 * and synthesized click/tap events. 1029 * @class Mottle 1030 * @constructor 1031 * @param {Object} params Constructor params 1032 * @param {Number} [params.clickThreshold=250] Threshold, in milliseconds beyond which a touchstart followed by a touchend is not considered to be a click. 1033 * @param {Number} [params.dblClickThreshold=450] Threshold, in milliseconds beyond which two successive tap events are not considered to be a click. 1034 * @param {Boolean} [params.smartClicks=false] If true, won't fire click events if the mouse has moved between mousedown and mouseup. Note that this functionality 1035 * requires that Mottle consume the mousedown event, and so may not be viable in all use cases. 1036 */ 1037 root.Mottle = function (params) { 1038 params = params || {}; 1039 var clickThreshold = params.clickThreshold || 250, 1040 dblClickThreshold = params.dblClickThreshold || 450, 1041 mouseEnterExitHandler = new MouseEnterExitHandler(), 1042 tapHandler = new TapHandler(clickThreshold, dblClickThreshold), 1043 _smartClicks = params.smartClicks, 1044 _doBind = function (obj, evt, fn, children) { 1045 if (fn == null) return; 1046 _each(obj, function () { 1047 var _el = _gel(this); 1048 if (_smartClicks && evt === "click") 1049 SmartClickHandler(_el, evt, fn, children); 1050 else if (evt === "tap" || evt === "dbltap" || evt === "contextmenu") { 1051 tapHandler(_el, evt, fn, children); 1052 } 1053 else if (evt === "mouseenter" || evt == "mouseexit") 1054 mouseEnterExitHandler(_el, evt, fn, children); 1055 else 1056 DefaultHandler(_el, evt, fn, children); 1057 }); 1058 }; 1059 1060 /** 1061 * Removes an element from the DOM, and deregisters all event handlers for it. You should use this 1062 * to ensure you don't leak memory. 1063 * @method remove 1064 * @param {String|Element} el Element, or id of the element, to remove. 1065 * @return {Mottle} The current Mottle instance; you can chain this method. 1066 */ 1067 this.remove = function (el) { 1068 _each(el, function () { 1069 var _el = _gel(this); 1070 if (_el.__ta) { 1071 for (var evt in _el.__ta) { 1072 if (_el.__ta.hasOwnProperty(evt)) { 1073 for (var h in _el.__ta[evt]) { 1074 if (_el.__ta[evt].hasOwnProperty(h)) 1075 _unbind(_el, evt, _el.__ta[evt][h]); 1076 } 1077 } 1078 } 1079 } 1080 _el.parentNode && _el.parentNode.removeChild(_el); 1081 }); 1082 return this; 1083 }; 1084 1085 /** 1086 * Register an event handler, optionally as a delegate for some set of descendant elements. Note 1087 * that this method takes either 3 or 4 arguments - if you supply 3 arguments it is assumed you have 1088 * omitted the `children` parameter, and that the event handler should be bound directly to the given element. 1089 * @method on 1090 * @param {Element[]|Element|String} el Either an Element, or a CSS spec for a list of elements, or an array of Elements. 1091 * @param {String} [children] Comma-delimited list of selectors identifying allowed children. 1092 * @param {String} event Event ID. 1093 * @param {Function} fn Event handler function. 1094 * @return {Mottle} The current Mottle instance; you can chain this method. 1095 */ 1096 this.on = function (el, event, children, fn) { 1097 var _el = arguments[0], 1098 _c = arguments.length == 4 ? arguments[2] : null, 1099 _e = arguments[1], 1100 _f = arguments[arguments.length - 1]; 1101 1102 _doBind(_el, _e, _f, _c); 1103 return this; 1104 }; 1105 1106 /** 1107 * Cancel delegate event handling for the given function. Note that unlike with 'on' you do not supply 1108 * a list of child selectors here: it removes event delegation from all of the child selectors for which the 1109 * given function was registered (if any). 1110 * @method off 1111 * @param {Element[]|Element|String} el Element - or ID of element - from which to remove event listener. 1112 * @param {String} event Event ID. 1113 * @param {Function} fn Event handler function. 1114 * @return {Mottle} The current Mottle instance; you can chain this method. 1115 */ 1116 this.off = function (el, event, fn) { 1117 _unbind(el, event, fn); 1118 return this; 1119 }; 1120 1121 /** 1122 * Triggers some event for a given element. 1123 * @method trigger 1124 * @param {Element} el Element for which to trigger the event. 1125 * @param {String} event Event ID. 1126 * @param {Event} originalEvent The original event. Should be optional of course, but currently is not, due 1127 * to the jsPlumb use case that caused this method to be added. 1128 * @param {Object} [payload] Optional object to set as `payload` on the generated event; useful for message passing. 1129 * @return {Mottle} The current Mottle instance; you can chain this method. 1130 */ 1131 this.trigger = function (el, event, originalEvent, payload) { 1132 // MouseEvent undefined in old IE; that's how we know it's a mouse event. A fine Microsoft paradox. 1133 var originalIsMouse = isMouseDevice && (typeof MouseEvent === "undefined" || originalEvent == null || originalEvent.constructor === MouseEvent); 1134 1135 var eventToBind = (isTouchDevice && !isMouseDevice && touchMap[event]) ? touchMap[event] : event, 1136 bindingAMouseEvent = !(isTouchDevice && !isMouseDevice && touchMap[event]); 1137 1138 var pl = _pageLocation(originalEvent), sl = _screenLocation(originalEvent), cl = _clientLocation(originalEvent); 1139 _each(el, function () { 1140 var _el = _gel(this), evt; 1141 originalEvent = originalEvent || { 1142 screenX: sl[0], 1143 screenY: sl[1], 1144 clientX: cl[0], 1145 clientY: cl[1] 1146 }; 1147 1148 var _decorate = function (_evt) { 1149 if (payload) _evt.payload = payload; 1150 }; 1151 1152 var eventGenerators = { 1153 "TouchEvent": function (evt) { 1154 var touch = document.createTouch(window, _el, 0, pl[0], pl[1], 1155 sl[0], sl[1], 1156 cl[0], cl[1], 1157 0, 0, 0, 0); 1158 1159 // https://gist.github.com/sstephenson/448808 1160 var touches = document.createTouchList(touch); 1161 var targetTouches = document.createTouchList(touch); 1162 var changedTouches = document.createTouchList(touch); 1163 evt.initTouchEvent(eventToBind, true, true, window, null, sl[0], sl[1], 1164 cl[0], cl[1], false, false, false, false, 1165 touches, targetTouches, changedTouches, 1, 0); 1166 }, 1167 "MouseEvents": function (evt) { 1168 evt.initMouseEvent(eventToBind, true, true, window, 0, 1169 sl[0], sl[1], 1170 cl[0], cl[1], 1171 false, false, false, false, 1, _el); 1172 1173 if (Sniff.android) { 1174 // Android's touch events are not standard. 1175 var t = document.createTouch(window, _el, 0, pl[0], pl[1], 1176 sl[0], sl[1], 1177 cl[0], cl[1], 1178 0, 0, 0, 0); 1179 1180 evt.touches = evt.targetTouches = evt.changedTouches = document.createTouchList(t); 1181 } 1182 } 1183 }; 1184 1185 if (document.createEvent) { 1186 1187 var ite = !bindingAMouseEvent && !originalIsMouse && (isTouchDevice && touchMap[event] && !Sniff.android), 1188 evtName = ite ? "TouchEvent" : "MouseEvents"; 1189 1190 evt = document.createEvent(evtName); 1191 eventGenerators[evtName](evt); 1192 _decorate(evt); 1193 _el.dispatchEvent(evt); 1194 } 1195 else if (document.createEventObject) { 1196 evt = document.createEventObject(); 1197 evt.eventType = evt.eventName = eventToBind; 1198 evt.screenX = sl[0]; 1199 evt.screenY = sl[1]; 1200 evt.clientX = cl[0]; 1201 evt.clientY = cl[1]; 1202 _decorate(evt); 1203 _el.fireEvent('on' + eventToBind, evt); 1204 } 1205 }); 1206 return this; 1207 } 1208 }; 1209 1210 /** 1211 * Static method to assist in 'consuming' an element: uses `stopPropagation` where available, or sets 1212 * `e.returnValue=false` where it is not. 1213 * @method Mottle.consume 1214 * @param {Event} e Event to consume 1215 * @param {Boolean} [doNotPreventDefault=false] If true, does not call `preventDefault()` on the event. 1216 */ 1217 root.Mottle.consume = function (e, doNotPreventDefault) { 1218 if (e.stopPropagation) 1219 e.stopPropagation(); 1220 else 1221 e.returnValue = false; 1222 1223 if (!doNotPreventDefault && e.preventDefault) 1224 e.preventDefault(); 1225 }; 1226 1227 /** 1228 * Gets the page location corresponding to the given event. For touch events this means get the page location of the first touch. 1229 * @method Mottle.pageLocation 1230 * @param {Event} e Event to get page location for. 1231 * @return {Number[]} [left, top] for the given event. 1232 */ 1233 root.Mottle.pageLocation = _pageLocation; 1234 1235 /** 1236 * Forces touch events to be turned "on". Useful for testing: even if you don't have a touch device, you can still 1237 * trigger a touch event when this is switched on and it will be captured and acted on. 1238 * @method setForceTouchEvents 1239 * @param {Boolean} value If true, force touch events to be on. 1240 */ 1241 root.Mottle.setForceTouchEvents = function (value) { 1242 isTouchDevice = value; 1243 }; 1244 1245 /** 1246 * Forces mouse events to be turned "on". Useful for testing: even if you don't have a mouse, you can still 1247 * trigger a mouse event when this is switched on and it will be captured and acted on. 1248 * @method setForceMouseEvents 1249 * @param {Boolean} value If true, force mouse events to be on. 1250 */ 1251 root.Mottle.setForceMouseEvents = function (value) { 1252 isMouseDevice = value; 1253 }; 1254 1255 root.Mottle.version = "0.8.0"; 1256 1257 if (typeof exports !== "undefined") { 1258 exports.Mottle = root.Mottle; 1259 } 1260 1261 }).call(typeof window === "undefined" ? this : window); 1262 1263 /** 1264 drag/drop functionality for use with jsPlumb but with 1265 no knowledge of jsPlumb. supports multiple scopes (separated by whitespace), dragging 1266 multiple elements, constrain to parent, drop filters, drag start filters, custom 1267 css classes. 1268 1269 a lot of the functionality of this script is expected to be plugged in: 1270 1271 addClass 1272 removeClass 1273 1274 addEvent 1275 removeEvent 1276 1277 getPosition 1278 setPosition 1279 getSize 1280 1281 indexOf 1282 intersects 1283 1284 the name came from here: 1285 1286 http://mrsharpoblunto.github.io/foswig.js/ 1287 1288 copyright 2016 jsPlumb 1289 */ 1290 1291 ;(function() { 1292 1293 "use strict"; 1294 var root = this; 1295 1296 var _suggest = function(list, item, head) { 1297 if (list.indexOf(item) === -1) { 1298 head ? list.unshift(item) : list.push(item); 1299 return true; 1300 } 1301 return false; 1302 }; 1303 1304 var _vanquish = function(list, item) { 1305 var idx = list.indexOf(item); 1306 if (idx != -1) list.splice(idx, 1); 1307 }; 1308 1309 var _difference = function(l1, l2) { 1310 var d = []; 1311 for (var i = 0; i < l1.length; i++) { 1312 if (l2.indexOf(l1[i]) == -1) 1313 d.push(l1[i]); 1314 } 1315 return d; 1316 }; 1317 1318 var _isString = function(f) { 1319 return f == null ? false : (typeof f === "string" || f.constructor == String); 1320 }; 1321 1322 var getOffsetRect = function (elem) { 1323 // (1) 1324 var box = elem.getBoundingClientRect(), 1325 body = document.body, 1326 docElem = document.documentElement, 1327 // (2) 1328 scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop, 1329 scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft, 1330 // (3) 1331 clientTop = docElem.clientTop || body.clientTop || 0, 1332 clientLeft = docElem.clientLeft || body.clientLeft || 0, 1333 // (4) 1334 top = box.top + scrollTop - clientTop, 1335 left = box.left + scrollLeft - clientLeft; 1336 1337 return { top: Math.round(top), left: Math.round(left) }; 1338 }; 1339 1340 var matchesSelector = function(el, selector, ctx) { 1341 ctx = ctx || el.parentNode; 1342 var possibles = ctx.querySelectorAll(selector); 1343 for (var i = 0; i < possibles.length; i++) { 1344 if (possibles[i] === el) 1345 return true; 1346 } 1347 return false; 1348 }; 1349 1350 var iev = (function() { 1351 var rv = -1; 1352 if (navigator.appName == 'Microsoft Internet Explorer') { 1353 var ua = navigator.userAgent, 1354 re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); 1355 if (re.exec(ua) != null) 1356 rv = parseFloat(RegExp.$1); 1357 } 1358 return rv; 1359 })(), 1360 DEFAULT_GRID_X = 50, 1361 DEFAULT_GRID_Y = 50, 1362 isIELT9 = iev > -1 && iev < 9, 1363 isIE9 = iev == 9, 1364 _pl = function(e) { 1365 if (isIELT9) { 1366 return [ e.clientX + document.documentElement.scrollLeft, e.clientY + document.documentElement.scrollTop ]; 1367 } 1368 else { 1369 var ts = _touches(e), t = _getTouch(ts, 0); 1370 // for IE9 pageX might be null if the event was synthesized. We try for pageX/pageY first, 1371 // falling back to clientX/clientY if necessary. In every other browser we want to use pageX/pageY. 1372 return isIE9 ? [t.pageX || t.clientX, t.pageY || t.clientY] : [t.pageX, t.pageY]; 1373 } 1374 }, 1375 _getTouch = function(touches, idx) { return touches.item ? touches.item(idx) : touches[idx]; }, 1376 _touches = function(e) { 1377 return e.touches && e.touches.length > 0 ? e.touches : 1378 e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches : 1379 e.targetTouches && e.targetTouches.length > 0 ? e.targetTouches : 1380 [ e ]; 1381 }, 1382 _classes = { 1383 draggable:"katavorio-draggable", // draggable elements 1384 droppable:"katavorio-droppable", // droppable elements 1385 drag : "katavorio-drag", // elements currently being dragged 1386 selected:"katavorio-drag-selected", // elements in current drag selection 1387 active : "katavorio-drag-active", // droppables that are targets of a currently dragged element 1388 hover : "katavorio-drag-hover", // droppables over which a matching drag element is hovering 1389 noSelect : "katavorio-drag-no-select", // added to the body to provide a hook to suppress text selection 1390 ghostProxy:"katavorio-ghost-proxy" // added to a ghost proxy element in use when a drag has exited the bounds of its parent. 1391 }, 1392 _defaultScope = "katavorio-drag-scope", 1393 _events = [ "stop", "start", "drag", "drop", "over", "out", "beforeStart" ], 1394 _devNull = function() {}, 1395 _true = function() { return true; }, 1396 _foreach = function(l, fn, from) { 1397 for (var i = 0; i < l.length; i++) { 1398 if (l[i] != from) 1399 fn(l[i]); 1400 } 1401 }, 1402 _setDroppablesActive = function(dd, val, andHover, drag) { 1403 _foreach(dd, function(e) { 1404 e.setActive(val); 1405 if (val) e.updatePosition(); 1406 if (andHover) e.setHover(drag, val); 1407 }); 1408 }, 1409 _each = function(obj, fn) { 1410 if (obj == null) return; 1411 obj = !_isString(obj) && (obj.tagName == null && obj.length != null) ? obj : [ obj ]; 1412 for (var i = 0; i < obj.length; i++) 1413 fn.apply(obj[i], [ obj[i] ]); 1414 }, 1415 _consume = function(e) { 1416 if (e.stopPropagation) { 1417 e.stopPropagation(); 1418 e.preventDefault(); 1419 } 1420 else { 1421 e.returnValue = false; 1422 } 1423 }, 1424 _defaultInputFilterSelector = "input,textarea,select,button,option", 1425 // 1426 // filters out events on all input elements, like textarea, checkbox, input, select. 1427 _inputFilter = function(e, el, _katavorio) { 1428 var t = e.srcElement || e.target; 1429 return !matchesSelector(t, _katavorio.getInputFilterSelector(), el); 1430 }; 1431 1432 var Super = function(el, params, css, scope) { 1433 this.params = params || {}; 1434 this.el = el; 1435 this.params.addClass(this.el, this._class); 1436 this.uuid = _uuid(); 1437 var enabled = true; 1438 this.setEnabled = function(e) { enabled = e; }; 1439 this.isEnabled = function() { return enabled; }; 1440 this.toggleEnabled = function() { enabled = !enabled; }; 1441 this.setScope = function(scopes) { 1442 this.scopes = scopes ? scopes.split(/\s+/) : [ scope ]; 1443 }; 1444 this.addScope = function(scopes) { 1445 var m = {}; 1446 _each(this.scopes, function(s) { m[s] = true;}); 1447 _each(scopes ? scopes.split(/\s+/) : [], function(s) { m[s] = true;}); 1448 this.scopes = []; 1449 for (var i in m) this.scopes.push(i); 1450 }; 1451 this.removeScope = function(scopes) { 1452 var m = {}; 1453 _each(this.scopes, function(s) { m[s] = true;}); 1454 _each(scopes ? scopes.split(/\s+/) : [], function(s) { delete m[s];}); 1455 this.scopes = []; 1456 for (var i in m) this.scopes.push(i); 1457 }; 1458 this.toggleScope = function(scopes) { 1459 var m = {}; 1460 _each(this.scopes, function(s) { m[s] = true;}); 1461 _each(scopes ? scopes.split(/\s+/) : [], function(s) { 1462 if (m[s]) delete m[s]; 1463 else m[s] = true; 1464 }); 1465 this.scopes = []; 1466 for (var i in m) this.scopes.push(i); 1467 }; 1468 this.setScope(params.scope); 1469 this.k = params.katavorio; 1470 return params.katavorio; 1471 }; 1472 1473 var TRUE = function() { return true; }; 1474 var FALSE = function() { return false; }; 1475 1476 var Drag = function(el, params, css, scope) { 1477 this._class = css.draggable; 1478 var k = Super.apply(this, arguments); 1479 this.rightButtonCanDrag = this.params.rightButtonCanDrag; 1480 var downAt = [0,0], posAtDown = null, pagePosAtDown = null, pageDelta = [0,0], moving = false, 1481 consumeStartEvent = this.params.consumeStartEvent !== false, 1482 dragEl = this.el, 1483 clone = this.params.clone, 1484 scroll = this.params.scroll, 1485 _multipleDrop = params.multipleDrop !== false, 1486 isConstrained = false, 1487 useGhostProxy = params.ghostProxy === true ? TRUE : params.ghostProxy && typeof params.ghostProxy === "function" ? params.ghostProxy : FALSE, 1488 ghostProxy = function(el) { return el.cloneNode(true); }; 1489 1490 var snapThreshold = params.snapThreshold || 5, 1491 _snap = function(pos, x, y, thresholdX, thresholdY) { 1492 thresholdX = thresholdX || snapThreshold; 1493 thresholdY = thresholdY || snapThreshold; 1494 var _dx = Math.floor(pos[0] / x), 1495 _dxl = x * _dx, 1496 _dxt = _dxl + x, 1497 _x = Math.abs(pos[0] - _dxl) <= thresholdX ? _dxl : Math.abs(_dxt - pos[0]) <= thresholdX ? _dxt : pos[0]; 1498 1499 var _dy = Math.floor(pos[1] / y), 1500 _dyl = y * _dy, 1501 _dyt = _dyl + y, 1502 _y = Math.abs(pos[1] - _dyl) <= thresholdY ? _dyl : Math.abs(_dyt - pos[1]) <= thresholdY ? _dyt : pos[1]; 1503 1504 return [ _x, _y]; 1505 }; 1506 1507 this.posses = []; 1508 this.posseRoles = {}; 1509 1510 this.toGrid = function(pos) { 1511 if (this.params.grid == null) { 1512 return pos; 1513 } 1514 else { 1515 return _snap(pos, this.params.grid[0], this.params.grid[1]); 1516 } 1517 }; 1518 1519 this.snap = function(x, y) { 1520 if (dragEl == null) return; 1521 x = x || (this.params.grid ? this.params.grid[0] : DEFAULT_GRID_X); 1522 y = y || (this.params.grid ? this.params.grid[1] : DEFAULT_GRID_Y); 1523 var p = this.params.getPosition(dragEl); 1524 this.params.setPosition(dragEl, _snap(p, x, y, x, y)); 1525 }; 1526 1527 this.setUseGhostProxy = function(val) { 1528 useGhostProxy = val ? TRUE : FALSE; 1529 }; 1530 1531 var constrain; 1532 var negativeFilter = function(pos) { 1533 return (params.allowNegative === false) ? [ Math.max (0, pos[0]), Math.max(0, pos[1]) ] : pos; 1534 }; 1535 1536 var _setConstrain = function(value) { 1537 constrain = typeof value === "function" ? value : value ? function(pos) { 1538 return negativeFilter([ 1539 Math.max(0, Math.min(constrainRect.w - this.size[0], pos[0])), 1540 Math.max(0, Math.min(constrainRect.h - this.size[1], pos[1])) 1541 ]); 1542 }.bind(this) : function(pos) { return negativeFilter(pos); }; 1543 }.bind(this); 1544 1545 _setConstrain(typeof this.params.constrain === "function" ? this.params.constrain : (this.params.constrain || this.params.containment)); 1546 1547 1548 /** 1549 * Sets whether or not the Drag is constrained. A value of 'true' means constrain to parent bounds; a function 1550 * will be executed and returns true if the position is allowed. 1551 * @param value 1552 */ 1553 this.setConstrain = function(value) { 1554 _setConstrain(value); 1555 }; 1556 1557 var revertFunction; 1558 /** 1559 * Sets a function to call on drag stop, which, if it returns true, indicates that the given element should 1560 * revert to its position before the previous drag. 1561 * @param fn 1562 */ 1563 this.setRevert = function(fn) { 1564 revertFunction = fn; 1565 }; 1566 1567 var _assignId = function(obj) { 1568 if (typeof obj == "function") { 1569 obj._katavorioId = _uuid(); 1570 return obj._katavorioId; 1571 } else { 1572 return obj; 1573 } 1574 }, 1575 // a map of { spec -> [ fn, exclusion ] } entries. 1576 _filters = {}, 1577 _testFilter = function(e) { 1578 for (var key in _filters) { 1579 var f = _filters[key]; 1580 var rv = f[0](e); 1581 if (f[1]) rv = !rv; 1582 if (!rv) return false; 1583 } 1584 return true; 1585 }, 1586 _setFilter = this.setFilter = function(f, _exclude) { 1587 if (f) { 1588 var key = _assignId(f); 1589 _filters[key] = [ 1590 function(e) { 1591 var t = e.srcElement || e.target, m; 1592 if (_isString(f)) { 1593 m = matchesSelector(t, f, el); 1594 } 1595 else if (typeof f === "function") { 1596 m = f(e, el); 1597 } 1598 return m; 1599 }, 1600 _exclude !== false 1601 ]; 1602 1603 } 1604 }, 1605 _addFilter = this.addFilter = _setFilter, 1606 _removeFilter = this.removeFilter = function(f) { 1607 var key = typeof f == "function" ? f._katavorioId : f; 1608 delete _filters[key]; 1609 }; 1610 1611 this.clearAllFilters = function() { 1612 _filters = {}; 1613 }; 1614 1615 this.canDrag = this.params.canDrag || _true; 1616 1617 var constrainRect, 1618 matchingDroppables = [], intersectingDroppables = []; 1619 1620 this.downListener = function(e) { 1621 var isNotRightClick = this.rightButtonCanDrag || (e.which !== 3 && e.button !== 2); 1622 if (isNotRightClick && this.isEnabled() && this.canDrag()) { 1623 var _f = _testFilter(e) && _inputFilter(e, this.el, this.k); 1624 if (_f) { 1625 if (!clone) 1626 dragEl = this.el; 1627 else { 1628 dragEl = this.el.cloneNode(true); 1629 dragEl.setAttribute("id", null); 1630 dragEl.style.position = "absolute"; 1631 // the clone node is added to the body; getOffsetRect gives us a value 1632 // relative to the body. 1633 var b = getOffsetRect(this.el); 1634 dragEl.style.left = b.left + "px"; 1635 dragEl.style.top = b.top + "px"; 1636 document.body.appendChild(dragEl); 1637 } 1638 consumeStartEvent && _consume(e); 1639 downAt = _pl(e); 1640 // 1641 this.params.bind(document, "mousemove", this.moveListener); 1642 this.params.bind(document, "mouseup", this.upListener); 1643 k.markSelection(this); 1644 k.markPosses(this); 1645 this.params.addClass(document.body, css.noSelect); 1646 _dispatch("beforeStart", {el:this.el, pos:posAtDown, e:e, drag:this}); 1647 } 1648 else if (this.params.consumeFilteredEvents) { 1649 _consume(e); 1650 } 1651 } 1652 }.bind(this); 1653 1654 this.moveListener = function(e) { 1655 if (downAt) { 1656 if (!moving) { 1657 var _continue = _dispatch("start", {el:this.el, pos:posAtDown, e:e, drag:this}); 1658 if (_continue !== false) { 1659 if (!downAt) return; 1660 this.mark(true); 1661 moving = true; 1662 } 1663 } 1664 1665 // it is possible that the start event caused the drag to be aborted. So we check 1666 // again that we are currently dragging. 1667 if (downAt) { 1668 intersectingDroppables.length = 0; 1669 var pos = _pl(e), dx = pos[0] - downAt[0], dy = pos[1] - downAt[1], 1670 z = this.params.ignoreZoom ? 1 : k.getZoom(); 1671 dx /= z; 1672 dy /= z; 1673 this.moveBy(dx, dy, e); 1674 k.updateSelection(dx, dy, this); 1675 k.updatePosses(dx, dy, this); 1676 } 1677 } 1678 }.bind(this); 1679 1680 this.upListener = function(e) { 1681 if (downAt) { 1682 downAt = null; 1683 this.params.unbind(document, "mousemove", this.moveListener); 1684 this.params.unbind(document, "mouseup", this.upListener); 1685 this.params.removeClass(document.body, css.noSelect); 1686 this.unmark(e); 1687 k.unmarkSelection(this, e); 1688 k.unmarkPosses(this, e); 1689 this.stop(e); 1690 k.notifySelectionDragStop(this, e); 1691 k.notifyPosseDragStop(this, e); 1692 moving = false; 1693 if (clone) { 1694 dragEl && dragEl.parentNode && dragEl.parentNode.removeChild(dragEl); 1695 dragEl = null; 1696 } 1697 1698 intersectingDroppables.length = 0; 1699 1700 if (revertFunction && revertFunction(this.el, this.params.getPosition(this.el)) === true) { 1701 this.params.setPosition(this.el, posAtDown); 1702 _dispatch("revert", this.el); 1703 } 1704 } 1705 }.bind(this); 1706 1707 this.getFilters = function() { return _filters; }; 1708 1709 this.abort = function() { 1710 if (downAt != null) 1711 this.upListener(); 1712 }; 1713 1714 this.getDragElement = function() { 1715 return dragEl || this.el; 1716 }; 1717 1718 var listeners = {"start":[], "drag":[], "stop":[], "over":[], "out":[], "beforeStart":[], "revert":[] }; 1719 if (params.events.start) listeners.start.push(params.events.start); 1720 if (params.events.beforeStart) listeners.beforeStart.push(params.events.beforeStart); 1721 if (params.events.stop) listeners.stop.push(params.events.stop); 1722 if (params.events.drag) listeners.drag.push(params.events.drag); 1723 if (params.events.revert) listeners.revert.push(params.events.revert); 1724 1725 this.on = function(evt, fn) { 1726 if (listeners[evt]) listeners[evt].push(fn); 1727 }; 1728 1729 this.off = function(evt, fn) { 1730 if (listeners[evt]) { 1731 var l = []; 1732 for (var i = 0; i < listeners[evt].length; i++) { 1733 if (listeners[evt][i] !== fn) l.push(listeners[evt][i]); 1734 } 1735 listeners[evt] = l; 1736 } 1737 }; 1738 1739 var _dispatch = function(evt, value) { 1740 if (listeners[evt]) { 1741 for (var i = 0; i < listeners[evt].length; i++) { 1742 try { 1743 listeners[evt][i](value); 1744 } 1745 catch (e) { } 1746 } 1747 } 1748 }; 1749 1750 this.notifyStart = function(e) { 1751 _dispatch("start", {el:this.el, pos:this.params.getPosition(dragEl), e:e, drag:this}); 1752 }; 1753 1754 this.stop = function(e, force) { 1755 if (force || moving) { 1756 var positions = [], 1757 sel = k.getSelection(), 1758 dPos = this.params.getPosition(dragEl); 1759 1760 if (sel.length > 1) { 1761 for (var i = 0; i < sel.length; i++) { 1762 var p = this.params.getPosition(sel[i].el); 1763 positions.push([ sel[i].el, { left: p[0], top: p[1] }, sel[i] ]); 1764 } 1765 } 1766 else { 1767 positions.push([ dragEl, {left:dPos[0], top:dPos[1]}, this ]); 1768 } 1769 1770 _dispatch("stop", { 1771 el: dragEl, 1772 pos: ghostProxyOffsets || dPos, 1773 finalPos:dPos, 1774 e: e, 1775 drag: this, 1776 selection:positions 1777 }); 1778 } 1779 }; 1780 1781 this.mark = function(andNotify) { 1782 posAtDown = this.params.getPosition(dragEl); 1783 pagePosAtDown = this.params.getPosition(dragEl, true); 1784 pageDelta = [pagePosAtDown[0] - posAtDown[0], pagePosAtDown[1] - posAtDown[1]]; 1785 this.size = this.params.getSize(dragEl); 1786 matchingDroppables = k.getMatchingDroppables(this); 1787 _setDroppablesActive(matchingDroppables, true, false, this); 1788 this.params.addClass(dragEl, this.params.dragClass || css.drag); 1789 //if (this.params.constrain || this.params.containment) { 1790 var cs = this.params.getSize(dragEl.parentNode); 1791 constrainRect = { w:cs[0], h:cs[1] }; 1792 //} 1793 if (andNotify) { 1794 k.notifySelectionDragStart(this); 1795 } 1796 }; 1797 var ghostProxyOffsets; 1798 this.unmark = function(e, doNotCheckDroppables) { 1799 _setDroppablesActive(matchingDroppables, false, true, this); 1800 1801 1802 if (isConstrained && useGhostProxy(this.el)) { 1803 ghostProxyOffsets = [dragEl.offsetLeft, dragEl.offsetTop]; 1804 this.el.parentNode.removeChild(dragEl); 1805 dragEl = this.el; 1806 } 1807 else { 1808 ghostProxyOffsets = null; 1809 } 1810 1811 this.params.removeClass(dragEl, this.params.dragClass || css.drag); 1812 matchingDroppables.length = 0; 1813 isConstrained = false; 1814 if (!doNotCheckDroppables) { 1815 if (intersectingDroppables.length > 0 && ghostProxyOffsets) { 1816 params.setPosition(this.el, ghostProxyOffsets); 1817 } 1818 intersectingDroppables.sort(_rankSort); 1819 for (var i = 0; i < intersectingDroppables.length; i++) { 1820 var retVal = intersectingDroppables[i].drop(this, e); 1821 if (retVal === true) break; 1822 } 1823 } 1824 }; 1825 this.moveBy = function(dx, dy, e) { 1826 intersectingDroppables.length = 0; 1827 var desiredLoc = this.toGrid([posAtDown[0] + dx, posAtDown[1] + dy]), 1828 cPos = constrain(desiredLoc, dragEl); 1829 1830 if (useGhostProxy(this.el)) { 1831 if (desiredLoc[0] != cPos[0] || desiredLoc[1] != cPos[1]) { 1832 if (!isConstrained) { 1833 var gp = ghostProxy(this.el); 1834 params.addClass(gp, _classes.ghostProxy); 1835 this.el.parentNode.appendChild(gp); 1836 dragEl = gp; 1837 isConstrained = true; 1838 } 1839 cPos = desiredLoc; 1840 } 1841 else { 1842 if (isConstrained) { 1843 this.el.parentNode.removeChild(dragEl); 1844 dragEl = this.el; 1845 isConstrained = false; 1846 } 1847 } 1848 } 1849 1850 var rect = { x:cPos[0], y:cPos[1], w:this.size[0], h:this.size[1]}, 1851 pageRect = { x:rect.x + pageDelta[0], y:rect.y + pageDelta[1], w:rect.w, h:rect.h}, 1852 focusDropElement = null; 1853 1854 1855 1856 this.params.setPosition(dragEl, cPos); 1857 for (var i = 0; i < matchingDroppables.length; i++) { 1858 var r2 = { x:matchingDroppables[i].pagePosition[0], y:matchingDroppables[i].pagePosition[1], w:matchingDroppables[i].size[0], h:matchingDroppables[i].size[1]}; 1859 if (this.params.intersects(pageRect, r2) && (_multipleDrop || focusDropElement == null || focusDropElement == matchingDroppables[i].el) && matchingDroppables[i].canDrop(this)) { 1860 if (!focusDropElement) focusDropElement = matchingDroppables[i].el; 1861 intersectingDroppables.push(matchingDroppables[i]); 1862 matchingDroppables[i].setHover(this, true, e); 1863 } 1864 else if (matchingDroppables[i].isHover()) { 1865 matchingDroppables[i].setHover(this, false, e); 1866 } 1867 } 1868 1869 _dispatch("drag", {el:this.el, pos:cPos, e:e, drag:this}); 1870 1871 /* test to see if the parent needs to be scrolled (future) 1872 if (scroll) { 1873 var pnsl = dragEl.parentNode.scrollLeft, pnst = dragEl.parentNode.scrollTop; 1874 console.log("scroll!", pnsl, pnst); 1875 }*/ 1876 }; 1877 this.destroy = function() { 1878 this.params.unbind(this.el, "mousedown", this.downListener); 1879 this.params.unbind(document, "mousemove", this.moveListener); 1880 this.params.unbind(document, "mouseup", this.upListener); 1881 this.downListener = null; 1882 this.upListener = null; 1883 this.moveListener = null; 1884 }; 1885 1886 // init:register mousedown, and perhaps set a filter 1887 this.params.bind(this.el, "mousedown", this.downListener); 1888 1889 // if handle provded, use that. otherwise, try to set a filter. 1890 // note that a `handle` selector always results in filterExclude being set to false, ie. 1891 // the selector defines the handle element(s). 1892 if (this.params.handle) 1893 _setFilter(this.params.handle, false); 1894 else 1895 _setFilter(this.params.filter, this.params.filterExclude); 1896 }; 1897 1898 var Drop = function(el, params, css, scope) { 1899 this._class = css.droppable; 1900 this.params = params || {}; 1901 this.rank = params.rank || 0; 1902 this._activeClass = this.params.activeClass || css.active; 1903 this._hoverClass = this.params.hoverClass || css.hover; 1904 Super.apply(this, arguments); 1905 var hover = false; 1906 this.allowLoopback = this.params.allowLoopback !== false; 1907 1908 this.setActive = function(val) { 1909 this.params[val ? "addClass" : "removeClass"](this.el, this._activeClass); 1910 }; 1911 1912 this.updatePosition = function() { 1913 this.position = this.params.getPosition(this.el); 1914 this.pagePosition = this.params.getPosition(this.el, true); 1915 this.size = this.params.getSize(this.el); 1916 }; 1917 1918 this.canDrop = this.params.canDrop || function(drag) { 1919 return true; 1920 }; 1921 1922 this.isHover = function() { return hover; }; 1923 1924 this.setHover = function(drag, val, e) { 1925 // if turning off hover but this was not the drag that caused the hover, ignore. 1926 if (val || this.el._katavorioDragHover == null || this.el._katavorioDragHover == drag.el._katavorio) { 1927 this.params[val ? "addClass" : "removeClass"](this.el, this._hoverClass); 1928 //this.el._katavorioDragHover = val ? drag.el._katavorio : null; 1929 this.el._katavorioDragHover = val ? drag.el._katavorio : null; 1930 if (hover !== val) 1931 this.params.events[val ? "over" : "out"]({el:this.el, e:e, drag:drag, drop:this}); 1932 hover = val; 1933 } 1934 }; 1935 1936 this.drop = function(drag, event) { 1937 return this.params.events["drop"]({ drag:drag, e:event, drop:this }); 1938 }; 1939 1940 this.destroy = function() { 1941 this._class = null; 1942 this._activeClass = null; 1943 this._hoverClass = null; 1944 //this.params = null; 1945 hover = null; 1946 //this.el = null; 1947 }; 1948 }; 1949 1950 var _uuid = function() { 1951 return ('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 1952 var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); 1953 return v.toString(16); 1954 })); 1955 }; 1956 1957 var _rankSort = function(a,b) { 1958 return a.rank < b.rank ? 1 : a.rank > b.rank ? -1 : 0; 1959 }; 1960 1961 var _gel = function(el) { 1962 if (el == null) return null; 1963 el = (typeof el === "string" || el.constructor == String) ? document.getElementById(el) : el; 1964 if (el == null) return null; 1965 el._katavorio = el._katavorio || _uuid(); 1966 return el; 1967 }; 1968 1969 root.Katavorio = function(katavorioParams) { 1970 1971 var _selection = [], 1972 _selectionMap = {}; 1973 1974 this._dragsByScope = {}; 1975 this._dropsByScope = {}; 1976 var _zoom = 1, 1977 _reg = function(obj, map) { 1978 _each(obj, function(_obj) { 1979 for(var i = 0; i < _obj.scopes.length; i++) { 1980 map[_obj.scopes[i]] = map[_obj.scopes[i]] || []; 1981 map[_obj.scopes[i]].push(_obj); 1982 } 1983 }); 1984 }, 1985 _unreg = function(obj, map) { 1986 var c = 0; 1987 _each(obj, function(_obj) { 1988 for(var i = 0; i < _obj.scopes.length; i++) { 1989 if (map[_obj.scopes[i]]) { 1990 var idx = katavorioParams.indexOf(map[_obj.scopes[i]], _obj); 1991 if (idx != -1) { 1992 map[_obj.scopes[i]].splice(idx, 1); 1993 c++; 1994 } 1995 } 1996 } 1997 }); 1998 1999 return c > 0 ; 2000 }, 2001 _getMatchingDroppables = this.getMatchingDroppables = function(drag) { 2002 var dd = [], _m = {}; 2003 for (var i = 0; i < drag.scopes.length; i++) { 2004 var _dd = this._dropsByScope[drag.scopes[i]]; 2005 if (_dd) { 2006 for (var j = 0; j < _dd.length; j++) { 2007 if (_dd[j].canDrop(drag) && !_m[_dd[j].uuid] && (_dd[j].allowLoopback || _dd[j].el !== drag.el)) { 2008 _m[_dd[j].uuid] = true; 2009 dd.push(_dd[j]); 2010 } 2011 } 2012 } 2013 } 2014 dd.sort(_rankSort); 2015 return dd; 2016 }, 2017 _prepareParams = function(p) { 2018 p = p || {}; 2019 var _p = { 2020 events:{} 2021 }, i; 2022 for (i in katavorioParams) _p[i] = katavorioParams[i]; 2023 for (i in p) _p[i] = p[i]; 2024 // events 2025 2026 for (i = 0; i < _events.length; i++) { 2027 _p.events[_events[i]] = p[_events[i]] || _devNull; 2028 } 2029 _p.katavorio = this; 2030 return _p; 2031 }.bind(this), 2032 _mistletoe = function(existingDrag, params) { 2033 for (var i = 0; i < _events.length; i++) { 2034 if (params[_events[i]]) { 2035 existingDrag.on(_events[i], params[_events[i]]); 2036 } 2037 } 2038 }.bind(this), 2039 _css = {}, 2040 overrideCss = katavorioParams.css || {}, 2041 _scope = katavorioParams.scope || _defaultScope; 2042 2043 // prepare map of css classes based on defaults frst, then optional overrides 2044 for (var i in _classes) _css[i] = _classes[i]; 2045 for (var i in overrideCss) _css[i] = overrideCss[i]; 2046 2047 var inputFilterSelector = katavorioParams.inputFilterSelector || _defaultInputFilterSelector; 2048 /** 2049 * Gets the selector identifying which input elements to filter from drag events. 2050 * @method getInputFilterSelector 2051 * @return {String} Current input filter selector. 2052 */ 2053 this.getInputFilterSelector = function() { return inputFilterSelector; }; 2054 2055 /** 2056 * Sets the selector identifying which input elements to filter from drag events. 2057 * @method setInputFilterSelector 2058 * @param {String} selector Input filter selector to set. 2059 * @return {Katavorio} Current instance; method may be chained. 2060 */ 2061 this.setInputFilterSelector = function(selector) { 2062 inputFilterSelector = selector; 2063 return this; 2064 }; 2065 2066 this.draggable = function(el, params) { 2067 var o = []; 2068 _each(el, function(_el) { 2069 _el = _gel(_el); 2070 if (_el != null) { 2071 if (_el._katavorioDrag == null) { 2072 var p = _prepareParams(params); 2073 _el._katavorioDrag = new Drag(_el, p, _css, _scope); 2074 _reg(_el._katavorioDrag, this._dragsByScope); 2075 o.push(_el._katavorioDrag); 2076 katavorioParams.addClass(_el, _css.draggable); 2077 } 2078 else { 2079 _mistletoe(_el._katavorioDrag, params); 2080 } 2081 } 2082 }.bind(this)); 2083 return o; 2084 2085 }; 2086 2087 this.droppable = function(el, params) { 2088 var o = []; 2089 _each(el, function(_el) { 2090 _el = _gel(_el); 2091 if (_el != null) { 2092 var drop = new Drop(_el, _prepareParams(params), _css, _scope); 2093 _el._katavorioDrop = _el._katavorioDrop || []; 2094 _el._katavorioDrop.push(drop); 2095 _reg(drop, this._dropsByScope); 2096 o.push(drop); 2097 katavorioParams.addClass(_el, _css.droppable); 2098 } 2099 }.bind(this)); 2100 return o; 2101 }; 2102 2103 /** 2104 * @name Katavorio#select 2105 * @function 2106 * @desc Adds an element to the current selection (for multiple node drag) 2107 * @param {Element|String} DOM element - or id of the element - to add. 2108 */ 2109 this.select = function(el) { 2110 _each(el, function() { 2111 var _el = _gel(this); 2112 if (_el && _el._katavorioDrag) { 2113 if (!_selectionMap[_el._katavorio]) { 2114 _selection.push(_el._katavorioDrag); 2115 _selectionMap[_el._katavorio] = [ _el, _selection.length - 1 ]; 2116 katavorioParams.addClass(_el, _css.selected); 2117 } 2118 } 2119 }); 2120 return this; 2121 }; 2122 2123 /** 2124 * @name Katavorio#deselect 2125 * @function 2126 * @desc Removes an element from the current selection (for multiple node drag) 2127 * @param {Element|String} DOM element - or id of the element - to remove. 2128 */ 2129 this.deselect = function(el) { 2130 _each(el, function() { 2131 var _el = _gel(this); 2132 if (_el && _el._katavorio) { 2133 var e = _selectionMap[_el._katavorio]; 2134 if (e) { 2135 var _s = []; 2136 for (var i = 0; i < _selection.length; i++) 2137 if (_selection[i].el !== _el) _s.push(_selection[i]); 2138 _selection = _s; 2139 delete _selectionMap[_el._katavorio]; 2140 katavorioParams.removeClass(_el, _css.selected); 2141 } 2142 } 2143 }); 2144 return this; 2145 }; 2146 2147 this.deselectAll = function() { 2148 for (var i in _selectionMap) { 2149 var d = _selectionMap[i]; 2150 katavorioParams.removeClass(d[0], _css.selected); 2151 } 2152 2153 _selection.length = 0; 2154 _selectionMap = {}; 2155 }; 2156 2157 this.markSelection = function(drag) { 2158 _foreach(_selection, function(e) { e.mark(); }, drag); 2159 }; 2160 2161 this.markPosses = function(drag) { 2162 if (drag.posses) { 2163 _each(drag.posses, function(p) { 2164 if (drag.posseRoles[p] && _posses[p]) { 2165 _foreach(_posses[p].members, function (d) { 2166 d.mark(); 2167 }, drag); 2168 } 2169 }) 2170 } 2171 }; 2172 2173 this.unmarkSelection = function(drag, event) { 2174 _foreach(_selection, function(e) { e.unmark(event); }, drag); 2175 }; 2176 2177 this.unmarkPosses = function(drag, event) { 2178 if (drag.posses) { 2179 _each(drag.posses, function(p) { 2180 if (drag.posseRoles[p] && _posses[p]) { 2181 _foreach(_posses[p].members, function (d) { 2182 d.unmark(event, true); 2183 }, drag); 2184 } 2185 }); 2186 } 2187 }; 2188 2189 this.getSelection = function() { return _selection.slice(0); }; 2190 2191 this.updateSelection = function(dx, dy, drag) { 2192 _foreach(_selection, function(e) { e.moveBy(dx, dy); }, drag); 2193 }; 2194 2195 var _posseAction = function(fn, drag) { 2196 if (drag.posses) { 2197 _each(drag.posses, function(p) { 2198 if (drag.posseRoles[p] && _posses[p]) { 2199 _foreach(_posses[p].members, function (e) { 2200 fn(e); 2201 }, drag); 2202 } 2203 }); 2204 } 2205 }; 2206 2207 this.updatePosses = function(dx, dy, drag) { 2208 _posseAction(function(e) { e.moveBy(dx, dy); }, drag); 2209 }; 2210 2211 this.notifyPosseDragStop = function(drag, evt) { 2212 _posseAction(function(e) { e.stop(evt, true); }, drag); 2213 }; 2214 2215 this.notifySelectionDragStop = function(drag, evt) { 2216 _foreach(_selection, function(e) { e.stop(evt, true); }, drag); 2217 }; 2218 2219 this.notifySelectionDragStart = function(drag, evt) { 2220 _foreach(_selection, function(e) { e.notifyStart(evt);}, drag); 2221 }; 2222 2223 this.setZoom = function(z) { _zoom = z; }; 2224 this.getZoom = function() { return _zoom; }; 2225 2226 // does the work of changing scopes 2227 var _scopeManip = function(kObj, scopes, map, fn) { 2228 _each(kObj, function(_kObj) { 2229 _unreg(_kObj, map); // deregister existing scopes 2230 _kObj[fn](scopes); // set scopes 2231 _reg(_kObj, map); // register new ones 2232 }); 2233 }; 2234 2235 _each([ "set", "add", "remove", "toggle"], function(v) { 2236 this[v + "Scope"] = function(el, scopes) { 2237 _scopeManip(el._katavorioDrag, scopes, this._dragsByScope, v + "Scope"); 2238 _scopeManip(el._katavorioDrop, scopes, this._dropsByScope, v + "Scope"); 2239 }.bind(this); 2240 this[v + "DragScope"] = function(el, scopes) { 2241 _scopeManip(el.constructor === Drag ? el : el._katavorioDrag, scopes, this._dragsByScope, v + "Scope"); 2242 }.bind(this); 2243 this[v + "DropScope"] = function(el, scopes) { 2244 _scopeManip(el.constructor === Drop ? el : el._katavorioDrop, scopes, this._dropsByScope, v + "Scope"); 2245 }.bind(this); 2246 }.bind(this)); 2247 2248 this.snapToGrid = function(x, y) { 2249 for (var s in this._dragsByScope) { 2250 _foreach(this._dragsByScope[s], function(d) { d.snap(x, y); }); 2251 } 2252 }; 2253 2254 this.getDragsForScope = function(s) { return this._dragsByScope[s]; }; 2255 this.getDropsForScope = function(s) { return this._dropsByScope[s]; }; 2256 2257 var _destroy = function(el, type, map) { 2258 el = _gel(el); 2259 if (el[type]) { 2260 2261 // remove from selection, if present. 2262 var selIdx = _selection.indexOf(el[type]); 2263 if (selIdx >= 0) { 2264 _selection.splice(selIdx, 1); 2265 } 2266 2267 if (_unreg(el[type], map)) { 2268 _each(el[type], function(kObj) { kObj.destroy() }); 2269 } 2270 2271 delete el[type]; 2272 } 2273 }; 2274 2275 this.elementRemoved = function(el) { 2276 this.destroyDraggable(el); 2277 this.destroyDroppable(el); 2278 }; 2279 2280 this.destroyDraggable = function(el) { 2281 _destroy(el, "_katavorioDrag", this._dragsByScope); 2282 }; 2283 2284 this.destroyDroppable = function(el) { 2285 _destroy(el, "_katavorioDrop", this._dropsByScope); 2286 }; 2287 2288 this.reset = function() { 2289 this._dragsByScope = {}; 2290 this._dropsByScope = {}; 2291 _selection = []; 2292 _selectionMap = {}; 2293 _posses = {}; 2294 }; 2295 2296 // ----- groups 2297 var _posses = {}; 2298 2299 var _processOneSpec = function(el, _spec, dontAddExisting) { 2300 var posseId = _isString(_spec) ? _spec : _spec.id; 2301 var active = _isString(_spec) ? true : _spec.active !== false; 2302 var posse = _posses[posseId] || (function() { 2303 var g = {name:posseId, members:[]}; 2304 _posses[posseId] = g; 2305 return g; 2306 })(); 2307 _each(el, function(_el) { 2308 if (_el._katavorioDrag) { 2309 2310 if (dontAddExisting && _el._katavorioDrag.posseRoles[posse.name] != null) return; 2311 2312 _suggest(posse.members, _el._katavorioDrag); 2313 _suggest(_el._katavorioDrag.posses, posse.name); 2314 _el._katavorioDrag.posseRoles[posse.name] = active; 2315 } 2316 }); 2317 return posse; 2318 }; 2319 2320 /** 2321 * Add the given element to the posse with the given id, creating the group if it at first does not exist. 2322 * @method addToPosse 2323 * @param {Element} el Element to add. 2324 * @param {String...|Object...} spec Variable args parameters. Each argument can be a either a String, indicating 2325 * the ID of a Posse to which the element should be added as an active participant, or an Object containing 2326 * `{ id:"posseId", active:false/true}`. In the latter case, if `active` is not provided it is assumed to be 2327 * true. 2328 * @returns {Posse|Posse[]} The Posse(s) to which the element(s) was/were added. 2329 */ 2330 this.addToPosse = function(el, spec) { 2331 2332 var posses = []; 2333 2334 for (var i = 1; i < arguments.length; i++) { 2335 posses.push(_processOneSpec(el, arguments[i])); 2336 } 2337 2338 return posses.length == 1 ? posses[0] : posses; 2339 }; 2340 2341 /** 2342 * Sets the posse(s) for the element with the given id, creating those that do not yet exist, and removing from 2343 * the element any current Posses that are not specified by this method call. This method will not change the 2344 * active/passive state if it is given a posse in which the element is already a member. 2345 * @method setPosse 2346 * @param {Element} el Element to set posse(s) on. 2347 * @param {String...|Object...} spec Variable args parameters. Each argument can be a either a String, indicating 2348 * the ID of a Posse to which the element should be added as an active participant, or an Object containing 2349 * `{ id:"posseId", active:false/true}`. In the latter case, if `active` is not provided it is assumed to be 2350 * true. 2351 * @returns {Posse|Posse[]} The Posse(s) to which the element(s) now belongs. 2352 */ 2353 this.setPosse = function(el, spec) { 2354 2355 var posses = []; 2356 2357 for (var i = 1; i < arguments.length; i++) { 2358 posses.push(_processOneSpec(el, arguments[i], true).name); 2359 } 2360 2361 _each(el, function(_el) { 2362 if (_el._katavorioDrag) { 2363 var diff = _difference(_el._katavorioDrag.posses, posses); 2364 var p = []; 2365 Array.prototype.push.apply(p, _el._katavorioDrag.posses); 2366 for (var i = 0; i < diff.length; i++) { 2367 this.removeFromPosse(_el, diff[i]); 2368 } 2369 } 2370 }.bind(this)); 2371 2372 return posses.length == 1 ? posses[0] : posses; 2373 }; 2374 2375 /** 2376 * Remove the given element from the given posse(s). 2377 * @method removeFromPosse 2378 * @param {Element} el Element to remove. 2379 * @param {String...} posseId Varargs parameter: one value for each posse to remove the element from. 2380 */ 2381 this.removeFromPosse = function(el, posseId) { 2382 if (arguments.length < 2) throw new TypeError("No posse id provided for remove operation"); 2383 for(var i = 1; i < arguments.length; i++) { 2384 posseId = arguments[i]; 2385 _each(el, function (_el) { 2386 if (_el._katavorioDrag && _el._katavorioDrag.posses) { 2387 var d = _el._katavorioDrag; 2388 _each(posseId, function (p) { 2389 _vanquish(_posses[p].members, d); 2390 _vanquish(d.posses, p); 2391 delete d.posseRoles[p]; 2392 }); 2393 } 2394 }); 2395 } 2396 }; 2397 2398 /** 2399 * Remove the given element from all Posses to which it belongs. 2400 * @method removeFromAllPosses 2401 * @param {Element|Element[]} el Element to remove from Posses. 2402 */ 2403 this.removeFromAllPosses = function(el) { 2404 _each(el, function(_el) { 2405 if (_el._katavorioDrag && _el._katavorioDrag.posses) { 2406 var d = _el._katavorioDrag; 2407 _each(d.posses, function(p) { 2408 _vanquish(_posses[p].members, d); 2409 }); 2410 d.posses.length = 0; 2411 d.posseRoles = {}; 2412 } 2413 }); 2414 }; 2415 2416 /** 2417 * Changes the participation state for the element in the Posse with the given ID. 2418 * @param {Element|Element[]} el Element(s) to change state for. 2419 * @param {String} posseId ID of the Posse to change element state for. 2420 * @param {Boolean} state True to make active, false to make passive. 2421 */ 2422 this.setPosseState = function(el, posseId, state) { 2423 var posse = _posses[posseId]; 2424 if (posse) { 2425 _each(el, function(_el) { 2426 if (_el._katavorioDrag && _el._katavorioDrag.posses) { 2427 _el._katavorioDrag.posseRoles[posse.name] = state; 2428 } 2429 }); 2430 } 2431 }; 2432 2433 }; 2434 2435 root.Katavorio.version = "0.19.2"; 2436 2437 if (typeof exports !== "undefined") { 2438 exports.Katavorio = root.Katavorio; 2439 } 2440 2441 }).call(typeof window !== 'undefined' ? window : this); 2442 2443 /* 2444 * jsPlumb 2445 * 2446 * Title:jsPlumb 2.3.0 2447 * 2448 * Provides a way to visually connect elements on an HTML page, using SVG. 2449 * 2450 * This file contains utility functions that run in both browsers and headless. 2451 * 2452 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 2453 * 2454 * http://jsplumbtoolkit.com 2455 * http://github.com/sporritt/jsplumb 2456 * 2457 * Dual licensed under the MIT and GPL2 licenses. 2458 */ 2459 2460 ; 2461 (function () { 2462 2463 var _isa = function (a) { 2464 return Object.prototype.toString.call(a) === "[object Array]"; 2465 }, 2466 _isnum = function (n) { 2467 return Object.prototype.toString.call(n) === "[object Number]"; 2468 }, 2469 _iss = function (s) { 2470 return typeof s === "string"; 2471 }, 2472 _isb = function (s) { 2473 return typeof s === "boolean"; 2474 }, 2475 _isnull = function (s) { 2476 return s == null; 2477 }, 2478 _iso = function (o) { 2479 return o == null ? false : Object.prototype.toString.call(o) === "[object Object]"; 2480 }, 2481 _isd = function (o) { 2482 return Object.prototype.toString.call(o) === "[object Date]"; 2483 }, 2484 _isf = function (o) { 2485 return Object.prototype.toString.call(o) === "[object Function]"; 2486 }, 2487 _isNamedFunction = function(o) { 2488 return _isf(o) && o.name != null && o.name.length > 0; 2489 }, 2490 _ise = function (o) { 2491 for (var i in o) { 2492 if (o.hasOwnProperty(i)) { 2493 return false; 2494 } 2495 } 2496 return true; 2497 }; 2498 2499 var root = this; 2500 root.jsPlumbUtil = { 2501 isArray: _isa, 2502 isString: _iss, 2503 isBoolean: _isb, 2504 isNull: _isnull, 2505 isObject: _iso, 2506 isDate: _isd, 2507 isFunction: _isf, 2508 isEmpty: _ise, 2509 isNumber: _isnum, 2510 clone: function (a) { 2511 if (_iss(a)) { 2512 return "" + a; 2513 } 2514 else if (_isb(a)) { 2515 return !!a; 2516 } 2517 else if (_isd(a)) { 2518 return new Date(a.getTime()); 2519 } 2520 else if (_isf(a)) { 2521 return a; 2522 } 2523 else if (_isa(a)) { 2524 var b = []; 2525 for (var i = 0; i < a.length; i++) { 2526 b.push(this.clone(a[i])); 2527 } 2528 return b; 2529 } 2530 else if (_iso(a)) { 2531 var c = {}; 2532 for (var j in a) { 2533 c[j] = this.clone(a[j]); 2534 } 2535 return c; 2536 } 2537 else { 2538 return a; 2539 } 2540 }, 2541 merge: function (a, b, collations) { 2542 // first change the collations array - if present - into a lookup table, because its faster. 2543 var cMap = {}, ar, i; 2544 collations = collations || []; 2545 for (i = 0; i < collations.length; i++) { 2546 cMap[collations[i]] = true; 2547 } 2548 2549 var c = this.clone(a); 2550 for (i in b) { 2551 if (c[i] == null) { 2552 c[i] = b[i]; 2553 } 2554 else if (_iss(b[i]) || _isb(b[i])) { 2555 if (!cMap[i]) { 2556 c[i] = b[i]; // if we dont want to collate, just copy it in. 2557 } 2558 else { 2559 ar = []; 2560 // if c's object is also an array we can keep its values. 2561 ar.push.apply(ar, _isa(c[i]) ? c[i] : [ c[i] ]); 2562 ar.push.apply(ar, _isa(b[i]) ? b[i] : [ b[i] ]); 2563 c[i] = ar; 2564 } 2565 } 2566 else { 2567 if (_isa(b[i])) { 2568 ar = []; 2569 // if c's object is also an array we can keep its values. 2570 if (_isa(c[i])) { 2571 ar.push.apply(ar, c[i]); 2572 } 2573 ar.push.apply(ar, b[i]); 2574 c[i] = ar; 2575 } 2576 else if (_iso(b[i])) { 2577 // overwite c's value with an object if it is not already one. 2578 if (!_iso(c[i])) { 2579 c[i] = {}; 2580 } 2581 for (var j in b[i]) { 2582 c[i][j] = b[i][j]; 2583 } 2584 } 2585 } 2586 2587 } 2588 return c; 2589 }, 2590 replace: function (inObj, path, value) { 2591 if (inObj == null) { 2592 return; 2593 } 2594 var q = inObj, t = q; 2595 path.replace(/([^\.])+/g, function (term, lc, pos, str) { 2596 var array = term.match(/([^\[0-9]+){1}(\[)([0-9+])/), 2597 last = pos + term.length >= str.length, 2598 _getArray = function () { 2599 return t[array[1]] || (function () { 2600 t[array[1]] = []; 2601 return t[array[1]]; 2602 })(); 2603 }; 2604 2605 if (last) { 2606 // set term = value on current t, creating term as array if necessary. 2607 if (array) { 2608 _getArray()[array[3]] = value; 2609 } 2610 else { 2611 t[term] = value; 2612 } 2613 } 2614 else { 2615 // set to current t[term], creating t[term] if necessary. 2616 if (array) { 2617 var a = _getArray(); 2618 t = a[array[3]] || (function () { 2619 a[array[3]] = {}; 2620 return a[array[3]]; 2621 })(); 2622 } 2623 else { 2624 t = t[term] || (function () { 2625 t[term] = {}; 2626 return t[term]; 2627 })(); 2628 } 2629 } 2630 }); 2631 2632 return inObj; 2633 }, 2634 // 2635 // chain a list of functions, supplied by [ object, method name, args ], and return on the first 2636 // one that returns the failValue. if none return the failValue, return the successValue. 2637 // 2638 functionChain: function (successValue, failValue, fns) { 2639 for (var i = 0; i < fns.length; i++) { 2640 var o = fns[i][0][fns[i][1]].apply(fns[i][0], fns[i][2]); 2641 if (o === failValue) { 2642 return o; 2643 } 2644 } 2645 return successValue; 2646 }, 2647 // take the given model and expand out any parameters. 2648 // 'functionPrefix' is optional, and if present, helps jsplumb figure out what to do if a value is a Function. 2649 // if you do not provide it, jsplumb will run the given values through any functions it finds, and use the function's 2650 // output as the value in the result. if you do provide the prefix, only functions that are named and have this prefix 2651 // will be executed; other functions will be passed as values to the output. 2652 populate: function (model, values, functionPrefix) { 2653 // for a string, see if it has parameter matches, and if so, try to make the substitutions. 2654 var getValue = function (fromString) { 2655 var matches = fromString.match(/(\${.*?})/g); 2656 if (matches != null) { 2657 for (var i = 0; i < matches.length; i++) { 2658 var val = values[matches[i].substring(2, matches[i].length - 1)] || ""; 2659 if (val != null) { 2660 fromString = fromString.replace(matches[i], val); 2661 } 2662 } 2663 } 2664 return fromString; 2665 }, 2666 // process one entry. 2667 _one = function (d) { 2668 if (d != null) { 2669 if (_iss(d)) { 2670 return getValue(d); 2671 } 2672 else if (_isf(d) && (functionPrefix == null || (d.name || "").indexOf(functionPrefix) === 0)) { 2673 return d(values); 2674 } 2675 else if (_isa(d)) { 2676 var r = []; 2677 for (var i = 0; i < d.length; i++) { 2678 r.push(_one(d[i])); 2679 } 2680 return r; 2681 } 2682 else if (_iso(d)) { 2683 var s = {}; 2684 for (var j in d) { 2685 s[j] = _one(d[j]); 2686 } 2687 return s; 2688 } 2689 else { 2690 return d; 2691 } 2692 } 2693 }; 2694 2695 return _one(model); 2696 }, 2697 findWithFunction: function (a, f) { 2698 if (a) { 2699 for (var i = 0; i < a.length; i++) { 2700 if (f(a[i])) { 2701 return i; 2702 } 2703 } 2704 } 2705 return -1; 2706 }, 2707 removeWithFunction: function (a, f) { 2708 var idx = root.jsPlumbUtil.findWithFunction(a, f); 2709 if (idx > -1) { 2710 a.splice(idx, 1); 2711 } 2712 return idx !== -1; 2713 }, 2714 remove: function (l, v) { 2715 var idx = l.indexOf(v); 2716 if (idx > -1) { 2717 l.splice(idx, 1); 2718 } 2719 return idx !== -1; 2720 }, 2721 // TODO support insert index 2722 addWithFunction: function (list, item, hashFunction) { 2723 if (root.jsPlumbUtil.findWithFunction(list, hashFunction) === -1) { 2724 list.push(item); 2725 } 2726 }, 2727 addToList: function (map, key, value, insertAtStart) { 2728 var l = map[key]; 2729 if (l == null) { 2730 l = []; 2731 map[key] = l; 2732 } 2733 l[insertAtStart ? "unshift" : "push"](value); 2734 return l; 2735 }, 2736 suggest : function(list, item, insertAtHead) { 2737 if (list.indexOf(item) === -1) { 2738 if (insertAtHead) { 2739 list.unshift(item); 2740 } else { 2741 list.push(item); 2742 } 2743 return true; 2744 } 2745 return false; 2746 }, 2747 // 2748 // extends the given obj (which can be an array) with the given constructor function, prototype functions, and 2749 // class members, any of which may be null. 2750 // 2751 extend: function (child, parent, _protoFn) { 2752 var i; 2753 parent = _isa(parent) ? parent : [ parent ]; 2754 2755 for (i = 0; i < parent.length; i++) { 2756 for (var j in parent[i].prototype) { 2757 if (parent[i].prototype.hasOwnProperty(j)) { 2758 child.prototype[j] = parent[i].prototype[j]; 2759 } 2760 } 2761 } 2762 2763 var _makeFn = function (name, protoFn) { 2764 return function () { 2765 for (i = 0; i < parent.length; i++) { 2766 if (parent[i].prototype[name]) { 2767 parent[i].prototype[name].apply(this, arguments); 2768 } 2769 } 2770 return protoFn.apply(this, arguments); 2771 }; 2772 }; 2773 2774 var _oneSet = function (fns) { 2775 for (var k in fns) { 2776 child.prototype[k] = _makeFn(k, fns[k]); 2777 } 2778 }; 2779 2780 if (arguments.length > 2) { 2781 for (i = 2; i < arguments.length; i++) { 2782 _oneSet(arguments[i]); 2783 } 2784 } 2785 2786 return child; 2787 }, 2788 uuid: function () { 2789 return ('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 2790 var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); 2791 return v.toString(16); 2792 })); 2793 }, 2794 logEnabled: true, 2795 log: function () { 2796 if (root.jsPlumbUtil.logEnabled && typeof console !== "undefined") { 2797 try { 2798 var msg = arguments[arguments.length - 1]; 2799 console.log(msg); 2800 } 2801 catch (e) { 2802 } 2803 } 2804 }, 2805 2806 /** 2807 * Wraps one function with another, creating a placeholder for the 2808 * wrapped function if it was null. this is used to wrap the various 2809 * drag/drop event functions - to allow jsPlumb to be notified of 2810 * important lifecycle events without imposing itself on the user's 2811 * drag/drop functionality. 2812 * @method jsPlumbUtil.wrap 2813 * @param {Function} wrappedFunction original function to wrap; may be null. 2814 * @param {Function} newFunction function to wrap the original with. 2815 * @param {Object} [returnOnThisValue] Optional. Indicates that the wrappedFunction should 2816 * not be executed if the newFunction returns a value matching 'returnOnThisValue'. 2817 * note that this is a simple comparison and only works for primitives right now. 2818 */ 2819 wrap: function (wrappedFunction, newFunction, returnOnThisValue) { 2820 wrappedFunction = wrappedFunction || function () { 2821 }; 2822 newFunction = newFunction || function () { 2823 }; 2824 return function () { 2825 var r = null; 2826 try { 2827 r = newFunction.apply(this, arguments); 2828 } catch (e) { 2829 root.jsPlumbUtil.log("jsPlumb function failed : " + e); 2830 } 2831 if (returnOnThisValue == null || (r !== returnOnThisValue)) { 2832 try { 2833 r = wrappedFunction.apply(this, arguments); 2834 } catch (e) { 2835 root.jsPlumbUtil.log("wrapped function failed : " + e); 2836 } 2837 } 2838 return r; 2839 }; 2840 } 2841 }; 2842 2843 root.jsPlumbUtil.EventGenerator = function () { 2844 var _listeners = {}, 2845 eventsSuspended = false, 2846 // this is a list of events that should re-throw any errors that occur during their dispatch. it is current private. 2847 eventsToDieOn = { "ready": true }; 2848 2849 this.bind = function (event, listener, insertAtStart) { 2850 var _one = function(evt) { 2851 root.jsPlumbUtil.addToList(_listeners, evt, listener, insertAtStart); 2852 listener.__jsPlumb = listener.__jsPlumb || {}; 2853 listener.__jsPlumb[root.jsPlumbUtil.uuid()] = evt; 2854 }; 2855 2856 if (typeof event === "string") { 2857 _one(event); 2858 } 2859 else if (event.length != null) { 2860 for (var i = 0; i < event.length; i++) { 2861 _one(event[i]); 2862 } 2863 } 2864 2865 return this; 2866 }; 2867 2868 this.fire = function (event, value, originalEvent) { 2869 if (!eventsSuspended && _listeners[event]) { 2870 var l = _listeners[event].length, i = 0, _gone = false, ret = null; 2871 if (!this.shouldFireEvent || this.shouldFireEvent(event, value, originalEvent)) { 2872 while (!_gone && i < l && ret !== false) { 2873 // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this 2874 // method will have the whole call stack available in the debugger. 2875 if (eventsToDieOn[event]) { 2876 _listeners[event][i].apply(this, [ value, originalEvent]); 2877 } 2878 else { 2879 try { 2880 ret = _listeners[event][i].apply(this, [ value, originalEvent ]); 2881 } catch (e) { 2882 root.jsPlumbUtil.log("jsPlumb: fire failed for event " + event + " : " + e); 2883 } 2884 } 2885 i++; 2886 if (_listeners == null || _listeners[event] == null) { 2887 _gone = true; 2888 } 2889 } 2890 } 2891 } 2892 return this; 2893 }; 2894 2895 this.unbind = function (eventOrListener, listener) { 2896 2897 if (arguments.length === 0) { 2898 _listeners = {}; 2899 } 2900 else if (arguments.length === 1) { 2901 if (typeof eventOrListener === "string") { 2902 delete _listeners[eventOrListener]; 2903 } 2904 else if (eventOrListener.__jsPlumb) { 2905 var evt; 2906 for (var i in eventOrListener.__jsPlumb) { 2907 evt = eventOrListener.__jsPlumb[i]; 2908 root.jsPlumbUtil.remove(_listeners[evt] || [], eventOrListener); 2909 } 2910 } 2911 } 2912 else if (arguments.length === 2) { 2913 root.jsPlumbUtil.remove(_listeners[eventOrListener] || [], listener); 2914 } 2915 2916 return this; 2917 }; 2918 2919 this.getListener = function (forEvent) { 2920 return _listeners[forEvent]; 2921 }; 2922 this.setSuspendEvents = function (val) { 2923 eventsSuspended = val; 2924 }; 2925 this.isSuspendEvents = function () { 2926 return eventsSuspended; 2927 }; 2928 this.silently = function(fn) { 2929 this.setSuspendEvents(true); 2930 try { 2931 fn(); 2932 } 2933 catch (e) { 2934 root.jsPlumbUtil.log("Cannot execute silent function " + e); 2935 } 2936 this.setSuspendEvents(false); 2937 }; 2938 this.cleanupListeners = function () { 2939 for (var i in _listeners) { 2940 _listeners[i] = null; 2941 } 2942 }; 2943 }; 2944 2945 root.jsPlumbUtil.EventGenerator.prototype = { 2946 cleanup: function () { 2947 this.cleanupListeners(); 2948 } 2949 }; 2950 2951 if (typeof exports !== "undefined") { 2952 exports.jsPlumbUtil = root.jsPlumbUtil; 2953 } 2954 2955 }).call(typeof window !== 'undefined' ? window : this); 2956 2957 /* 2958 * jsPlumb Community Edition 2959 * 2960 * Provides a way to visually connect elements on an HTML page, using SVG. 2961 * 2962 * This file contains utility functions that run in browsers only. 2963 * 2964 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 2965 * 2966 * https://jsplumbtoolkit.com 2967 * https://github.com/jsplumb/jsplumb 2968 * 2969 * Dual licensed under the MIT and GPL2 licenses. 2970 */ 2971 ;(function() { 2972 2973 "use strict"; 2974 2975 var root = this; 2976 2977 root.jsPlumbUtil.matchesSelector = function(el, selector, ctx) { 2978 ctx = ctx || el.parentNode; 2979 var possibles = ctx.querySelectorAll(selector); 2980 for (var i = 0; i < possibles.length; i++) { 2981 if (possibles[i] === el) { 2982 return true; 2983 } 2984 } 2985 return false; 2986 }; 2987 2988 root.jsPlumbUtil.consume = function(e, doNotPreventDefault) { 2989 if (e.stopPropagation) { 2990 e.stopPropagation(); 2991 } 2992 else { 2993 e.returnValue = false; 2994 } 2995 2996 if (!doNotPreventDefault && e.preventDefault){ 2997 e.preventDefault(); 2998 } 2999 }; 3000 3001 /* 3002 * Function: sizeElement 3003 * Helper to size and position an element. You would typically use 3004 * this when writing your own Connector or Endpoint implementation. 3005 * 3006 * Parameters: 3007 * x - [int] x position for the element origin 3008 * y - [int] y position for the element origin 3009 * w - [int] width of the element 3010 * h - [int] height of the element 3011 * 3012 */ 3013 root.jsPlumbUtil.sizeElement = function(el, x, y, w, h) { 3014 if (el) { 3015 el.style.height = h + "px"; 3016 el.height = h; 3017 el.style.width = w + "px"; 3018 el.width = w; 3019 el.style.left = x + "px"; 3020 el.style.top = y + "px"; 3021 } 3022 }; 3023 3024 }).call(typeof window !== 'undefined' ? window : this); 3025 3026 /* 3027 * jsPlumb 3028 * 3029 * Title:jsPlumb 2.3.0 3030 * 3031 * Provides a way to visually connect elements on an HTML page, using SVG. 3032 * 3033 * This file contains the core code. 3034 * 3035 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 3036 * 3037 * http://jsplumbtoolkit.com 3038 * http://github.com/sporritt/jsplumb 3039 * 3040 * Dual licensed under the MIT and GPL2 licenses. 3041 */ 3042 (function () { 3043 3044 "use strict"; 3045 3046 var root = this; 3047 var connectorTypes = [], rendererTypes; 3048 3049 var _ju = root.jsPlumbUtil, 3050 3051 /** 3052 * creates a timestamp, using milliseconds since 1970, but as a string. 3053 */ 3054 _timestamp = function () { 3055 return "" + (new Date()).getTime(); 3056 }, 3057 3058 // helper method to update the hover style whenever it, or paintStyle, changes. 3059 // we use paintStyle as the foundation and merge hoverPaintStyle over the 3060 // top. 3061 _updateHoverStyle = function (component) { 3062 if (component._jsPlumb.paintStyle && component._jsPlumb.hoverPaintStyle) { 3063 var mergedHoverStyle = {}; 3064 jsPlumb.extend(mergedHoverStyle, component._jsPlumb.paintStyle); 3065 jsPlumb.extend(mergedHoverStyle, component._jsPlumb.hoverPaintStyle); 3066 delete component._jsPlumb.hoverPaintStyle; 3067 // we want the fill of paintStyle to override a gradient, if possible. 3068 if (mergedHoverStyle.gradient && component._jsPlumb.paintStyle.fill) { 3069 delete mergedHoverStyle.gradient; 3070 } 3071 component._jsPlumb.hoverPaintStyle = mergedHoverStyle; 3072 } 3073 }, 3074 events = ["tap", "dbltap", "click", "dblclick", "mouseover", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], 3075 eventFilters = { "mouseout": "mouseleave", "mouseexit": "mouseleave" }, 3076 _updateAttachedElements = function (component, state, timestamp, sourceElement) { 3077 var affectedElements = component.getAttachedElements(); 3078 if (affectedElements) { 3079 for (var i = 0, j = affectedElements.length; i < j; i++) { 3080 if (!sourceElement || sourceElement !== affectedElements[i]) { 3081 affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. 3082 } 3083 } 3084 } 3085 }, 3086 _splitType = function (t) { 3087 return t == null ? null : t.split(" "); 3088 }, 3089 _mapType = function(map, obj, typeId) { 3090 for (var i in obj) { 3091 map[i] = typeId; 3092 } 3093 }, 3094 _each = function(fn, obj) { 3095 obj = _ju.isArray(obj) || (obj.length != null && !_ju.isString(obj)) ? obj : [ obj ]; 3096 for (var i = 0; i < obj.length; i++) { 3097 try { 3098 fn.apply(obj[i], [ obj[i] ]); 3099 } 3100 catch (e) { 3101 _ju.log(".each iteration failed : " + e); 3102 } 3103 } 3104 }, 3105 _applyTypes = function (component, params, doNotRepaint) { 3106 if (component.getDefaultType) { 3107 var td = component.getTypeDescriptor(), map = {}; 3108 var defType = component.getDefaultType(); 3109 var o = _ju.merge({}, defType); 3110 _mapType(map, defType, "__default"); 3111 for (var i = 0, j = component._jsPlumb.types.length; i < j; i++) { 3112 var tid = component._jsPlumb.types[i]; 3113 if (tid !== "__default") { 3114 var _t = component._jsPlumb.instance.getType(tid, td); 3115 if (_t != null) { 3116 o = _ju.merge(o, _t, [ "cssClass" ]); 3117 _mapType(map, _t, tid); 3118 } 3119 } 3120 } 3121 3122 if (params) { 3123 o = _ju.populate(o, params, "_"); 3124 } 3125 3126 component.applyType(o, doNotRepaint, map); 3127 if (!doNotRepaint) { 3128 component.repaint(); 3129 } 3130 } 3131 }, 3132 3133 // ------------------------------ BEGIN jsPlumbUIComponent -------------------------------------------- 3134 3135 jsPlumbUIComponent = root.jsPlumbUIComponent = function (params) { 3136 3137 _ju.EventGenerator.apply(this, arguments); 3138 3139 var self = this, 3140 a = arguments, 3141 idPrefix = self.idPrefix, 3142 id = idPrefix + (new Date()).getTime(); 3143 3144 this._jsPlumb = { 3145 instance: params._jsPlumb, 3146 parameters: params.parameters || {}, 3147 paintStyle: null, 3148 hoverPaintStyle: null, 3149 paintStyleInUse: null, 3150 hover: false, 3151 beforeDetach: params.beforeDetach, 3152 beforeDrop: params.beforeDrop, 3153 overlayPlacements: [], 3154 hoverClass: params.hoverClass || params._jsPlumb.Defaults.HoverClass, 3155 types: [], 3156 typeCache:{} 3157 }; 3158 3159 this.cacheTypeItem = function(key, item, typeId) { 3160 this._jsPlumb.typeCache[typeId] = this._jsPlumb.typeCache[typeId] || {}; 3161 this._jsPlumb.typeCache[typeId][key] = item; 3162 }; 3163 this.getCachedTypeItem = function(key, typeId) { 3164 return this._jsPlumb.typeCache[typeId] ? this._jsPlumb.typeCache[typeId][key] : null; 3165 }; 3166 3167 this.getId = function () { 3168 return id; 3169 }; 3170 3171 // ----------------------------- default type -------------------------------------------- 3172 3173 3174 var o = params.overlays || [], oo = {}; 3175 if (this.defaultOverlayKeys) { 3176 for (var i = 0; i < this.defaultOverlayKeys.length; i++) { 3177 Array.prototype.push.apply(o, this._jsPlumb.instance.Defaults[this.defaultOverlayKeys[i]] || []); 3178 } 3179 3180 for (i = 0; i < o.length; i++) { 3181 // if a string, convert to object representation so that we can store the typeid on it. 3182 // also assign an id. 3183 var fo = jsPlumb.convertToFullOverlaySpec(o[i]); 3184 oo[fo[1].id] = fo; 3185 } 3186 } 3187 3188 var _defaultType = { 3189 overlays:oo, 3190 parameters: params.parameters || {}, 3191 scope: params.scope || this._jsPlumb.instance.getDefaultScope() 3192 }; 3193 this.getDefaultType = function() { 3194 return _defaultType; 3195 }; 3196 this.appendToDefaultType = function(obj) { 3197 for (var i in obj) { 3198 _defaultType[i] = obj[i]; 3199 } 3200 }; 3201 3202 // ----------------------------- end default type -------------------------------------------- 3203 3204 // all components can generate events 3205 3206 if (params.events) { 3207 for (var evtName in params.events) { 3208 self.bind(evtName, params.events[evtName]); 3209 } 3210 } 3211 3212 // all components get this clone function. 3213 // TODO issue 116 showed a problem with this - it seems 'a' that is in 3214 // the clone function's scope is shared by all invocations of it, the classic 3215 // JS closure problem. for now, jsPlumb does a version of this inline where 3216 // it used to call clone. but it would be nice to find some time to look 3217 // further at this. 3218 this.clone = function () { 3219 var o = Object.create(this.constructor.prototype); 3220 this.constructor.apply(o, a); 3221 return o; 3222 }.bind(this); 3223 3224 // user can supply a beforeDetach callback, which will be executed before a detach 3225 // is performed; returning false prevents the detach. 3226 this.isDetachAllowed = function (connection) { 3227 var r = true; 3228 if (this._jsPlumb.beforeDetach) { 3229 try { 3230 r = this._jsPlumb.beforeDetach(connection); 3231 } 3232 catch (e) { 3233 _ju.log("jsPlumb: beforeDetach callback failed", e); 3234 } 3235 } 3236 return r; 3237 }; 3238 3239 // user can supply a beforeDrop callback, which will be executed before a dropped 3240 // connection is confirmed. user can return false to reject connection. 3241 this.isDropAllowed = function (sourceId, targetId, scope, connection, dropEndpoint, source, target) { 3242 var r = this._jsPlumb.instance.checkCondition("beforeDrop", { 3243 sourceId: sourceId, 3244 targetId: targetId, 3245 scope: scope, 3246 connection: connection, 3247 dropEndpoint: dropEndpoint, 3248 source: source, target: target 3249 }); 3250 if (this._jsPlumb.beforeDrop) { 3251 try { 3252 r = this._jsPlumb.beforeDrop({ 3253 sourceId: sourceId, 3254 targetId: targetId, 3255 scope: scope, 3256 connection: connection, 3257 dropEndpoint: dropEndpoint, 3258 source: source, target: target 3259 }); 3260 } 3261 catch (e) { 3262 _ju.log("jsPlumb: beforeDrop callback failed", e); 3263 } 3264 } 3265 return r; 3266 }; 3267 3268 var domListeners = []; 3269 3270 // sets the component associated with listener events. for instance, an overlay delegates 3271 // its events back to a connector. but if the connector is swapped on the underlying connection, 3272 // then this component must be changed. This is called by setConnector in the Connection class. 3273 this.setListenerComponent = function (c) { 3274 for (var i = 0; i < domListeners.length; i++) { 3275 domListeners[i][3] = c; 3276 } 3277 }; 3278 3279 3280 }; 3281 3282 var _removeTypeCssHelper = function (component, typeIndex) { 3283 var typeId = component._jsPlumb.types[typeIndex], 3284 type = component._jsPlumb.instance.getType(typeId, component.getTypeDescriptor()); 3285 3286 if (type != null && type.cssClass && component.canvas) { 3287 component._jsPlumb.instance.removeClass(component.canvas, type.cssClass); 3288 } 3289 }; 3290 3291 _ju.extend(root.jsPlumbUIComponent, _ju.EventGenerator, { 3292 3293 getParameter: function (name) { 3294 return this._jsPlumb.parameters[name]; 3295 }, 3296 3297 setParameter: function (name, value) { 3298 this._jsPlumb.parameters[name] = value; 3299 }, 3300 3301 getParameters: function () { 3302 return this._jsPlumb.parameters; 3303 }, 3304 3305 setParameters: function (p) { 3306 this._jsPlumb.parameters = p; 3307 }, 3308 3309 getClass:function() { 3310 return jsPlumb.getClass(this.canvas); 3311 }, 3312 3313 hasClass:function(clazz) { 3314 return jsPlumb.hasClass(this.canvas, clazz); 3315 }, 3316 3317 addClass: function (clazz) { 3318 jsPlumb.addClass(this.canvas, clazz); 3319 }, 3320 3321 removeClass: function (clazz) { 3322 jsPlumb.removeClass(this.canvas, clazz); 3323 }, 3324 3325 updateClasses: function (classesToAdd, classesToRemove) { 3326 jsPlumb.updateClasses(this.canvas, classesToAdd, classesToRemove); 3327 }, 3328 3329 setType: function (typeId, params, doNotRepaint) { 3330 this.clearTypes(); 3331 this._jsPlumb.types = _splitType(typeId) || []; 3332 _applyTypes(this, params, doNotRepaint); 3333 }, 3334 3335 getType: function () { 3336 return this._jsPlumb.types; 3337 }, 3338 3339 reapplyTypes: function (params, doNotRepaint) { 3340 _applyTypes(this, params, doNotRepaint); 3341 }, 3342 3343 hasType: function (typeId) { 3344 return this._jsPlumb.types.indexOf(typeId) !== -1; 3345 }, 3346 3347 addType: function (typeId, params, doNotRepaint) { 3348 var t = _splitType(typeId), _cont = false; 3349 if (t != null) { 3350 for (var i = 0, j = t.length; i < j; i++) { 3351 if (!this.hasType(t[i])) { 3352 this._jsPlumb.types.push(t[i]); 3353 _cont = true; 3354 } 3355 } 3356 if (_cont) { 3357 _applyTypes(this, params, doNotRepaint); 3358 } 3359 } 3360 }, 3361 3362 removeType: function (typeId, params, doNotRepaint) { 3363 var t = _splitType(typeId), _cont = false, _one = function (tt) { 3364 var idx = this._jsPlumb.types.indexOf(tt); 3365 if (idx !== -1) { 3366 // remove css class if necessary 3367 _removeTypeCssHelper(this, idx); 3368 this._jsPlumb.types.splice(idx, 1); 3369 return true; 3370 } 3371 return false; 3372 }.bind(this); 3373 3374 if (t != null) { 3375 for (var i = 0, j = t.length; i < j; i++) { 3376 _cont = _one(t[i]) || _cont; 3377 } 3378 if (_cont) { 3379 _applyTypes(this, params, doNotRepaint); 3380 } 3381 } 3382 }, 3383 clearTypes: function (params, doNotRepaint) { 3384 var i = this._jsPlumb.types.length; 3385 for (var j = 0; j < i; j++) { 3386 _removeTypeCssHelper(this, 0); 3387 this._jsPlumb.types.splice(0, 1); 3388 } 3389 _applyTypes(this, params, doNotRepaint); 3390 }, 3391 3392 toggleType: function (typeId, params, doNotRepaint) { 3393 var t = _splitType(typeId); 3394 if (t != null) { 3395 for (var i = 0, j = t.length; i < j; i++) { 3396 var idx = this._jsPlumb.types.indexOf(t[i]); 3397 if (idx !== -1) { 3398 _removeTypeCssHelper(this, idx); 3399 this._jsPlumb.types.splice(idx, 1); 3400 } 3401 else { 3402 this._jsPlumb.types.push(t[i]); 3403 } 3404 } 3405 3406 _applyTypes(this, params, doNotRepaint); 3407 } 3408 }, 3409 applyType: function (t, doNotRepaint) { 3410 this.setPaintStyle(t.paintStyle, doNotRepaint); 3411 this.setHoverPaintStyle(t.hoverPaintStyle, doNotRepaint); 3412 if (t.parameters) { 3413 for (var i in t.parameters) { 3414 this.setParameter(i, t.parameters[i]); 3415 } 3416 } 3417 this._jsPlumb.paintStyleInUse = this.getPaintStyle(); 3418 }, 3419 setPaintStyle: function (style, doNotRepaint) { 3420 // this._jsPlumb.paintStyle = jsPlumb.extend({}, style); 3421 // TODO figure out if we want components to clone paintStyle so as not to share it. 3422 this._jsPlumb.paintStyle = style; 3423 this._jsPlumb.paintStyleInUse = this._jsPlumb.paintStyle; 3424 _updateHoverStyle(this); 3425 if (!doNotRepaint) { 3426 this.repaint(); 3427 } 3428 }, 3429 getPaintStyle: function () { 3430 return this._jsPlumb.paintStyle; 3431 }, 3432 setHoverPaintStyle: function (style, doNotRepaint) { 3433 //this._jsPlumb.hoverPaintStyle = jsPlumb.extend({}, style); 3434 // TODO figure out if we want components to clone paintStyle so as not to share it. 3435 this._jsPlumb.hoverPaintStyle = style; 3436 _updateHoverStyle(this); 3437 if (!doNotRepaint) { 3438 this.repaint(); 3439 } 3440 }, 3441 getHoverPaintStyle: function () { 3442 return this._jsPlumb.hoverPaintStyle; 3443 }, 3444 destroy: function (force) { 3445 if (force || this.typeId == null) { 3446 this.cleanupListeners(); // this is on EventGenerator 3447 this.clone = null; 3448 this._jsPlumb = null; 3449 } 3450 }, 3451 3452 isHover: function () { 3453 return this._jsPlumb.hover; 3454 }, 3455 3456 setHover: function (hover, ignoreAttachedElements, timestamp) { 3457 // while dragging, we ignore these events. this keeps the UI from flashing and 3458 // swishing and whatevering. 3459 if (this._jsPlumb && !this._jsPlumb.instance.currentlyDragging && !this._jsPlumb.instance.isHoverSuspended()) { 3460 3461 this._jsPlumb.hover = hover; 3462 var method = hover ? "addClass" : "removeClass"; 3463 3464 if (this.canvas != null) { 3465 if (this._jsPlumb.instance.hoverClass != null) { 3466 this._jsPlumb.instance[method](this.canvas, this._jsPlumb.instance.hoverClass); 3467 } 3468 if (this._jsPlumb.hoverClass != null) { 3469 this._jsPlumb.instance[method](this.canvas, this._jsPlumb.hoverClass); 3470 } 3471 } 3472 if (this._jsPlumb.hoverPaintStyle != null) { 3473 this._jsPlumb.paintStyleInUse = hover ? this._jsPlumb.hoverPaintStyle : this._jsPlumb.paintStyle; 3474 if (!this._jsPlumb.instance.isSuspendDrawing()) { 3475 timestamp = timestamp || _timestamp(); 3476 this.repaint({timestamp: timestamp, recalc: false}); 3477 } 3478 } 3479 // get the list of other affected elements, if supported by this component. 3480 // for a connection, its the endpoints. for an endpoint, its the connections! surprise. 3481 if (this.getAttachedElements && !ignoreAttachedElements) { 3482 _updateAttachedElements(this, hover, _timestamp(), this); 3483 } 3484 } 3485 } 3486 }); 3487 3488 // ------------------------------ END jsPlumbUIComponent -------------------------------------------- 3489 3490 var _jsPlumbInstanceIndex = 0, 3491 getInstanceIndex = function () { 3492 var i = _jsPlumbInstanceIndex + 1; 3493 _jsPlumbInstanceIndex++; 3494 return i; 3495 }; 3496 3497 var jsPlumbInstance = root.jsPlumbInstance = function (_defaults) { 3498 3499 this.version = "2.4.3"; 3500 3501 if (_defaults) { 3502 jsPlumb.extend(this.Defaults, _defaults); 3503 } 3504 3505 this.logEnabled = this.Defaults.LogEnabled; 3506 this._connectionTypes = {}; 3507 this._endpointTypes = {}; 3508 3509 _ju.EventGenerator.apply(this); 3510 3511 var _currentInstance = this, 3512 _instanceIndex = getInstanceIndex(), 3513 _bb = _currentInstance.bind, 3514 _initialDefaults = {}, 3515 _zoom = 1, 3516 _info = function (el) { 3517 if (el == null) { 3518 return null; 3519 } 3520 else if (el.nodeType === 3 || el.nodeType === 8) { 3521 return { el:el, text:true }; 3522 } 3523 else { 3524 var _el = _currentInstance.getElement(el); 3525 return { el: _el, id: (_ju.isString(el) && _el == null) ? el : _getId(_el) }; 3526 } 3527 }; 3528 3529 this.getInstanceIndex = function () { 3530 return _instanceIndex; 3531 }; 3532 3533 this.setZoom = function (z, repaintEverything) { 3534 _zoom = z; 3535 _currentInstance.fire("zoom", _zoom); 3536 if (repaintEverything) { 3537 _currentInstance.repaintEverything(); 3538 } 3539 return true; 3540 }; 3541 this.getZoom = function () { 3542 return _zoom; 3543 }; 3544 3545 for (var i in this.Defaults) { 3546 _initialDefaults[i] = this.Defaults[i]; 3547 } 3548 3549 var _container, _containerDelegations = []; 3550 this.unbindContainer = function() { 3551 if (_container != null && _containerDelegations.length > 0) { 3552 for (var i = 0; i < _containerDelegations.length; i++) { 3553 _currentInstance.off(_container, _containerDelegations[i][0], _containerDelegations[i][1]); 3554 } 3555 } 3556 }; 3557 this.setContainer = function (c) { 3558 3559 this.unbindContainer(); 3560 3561 // get container as dom element. 3562 c = this.getElement(c); 3563 // move existing connections and endpoints, if any. 3564 this.select().each(function (conn) { 3565 conn.moveParent(c); 3566 }); 3567 this.selectEndpoints().each(function (ep) { 3568 ep.moveParent(c); 3569 }); 3570 3571 // set container. 3572 var previousContainer = _container; 3573 _container = c; 3574 _containerDelegations.length = 0; 3575 var eventAliases = { 3576 "endpointclick":"endpointClick", 3577 "endpointdblclick":"endpointDblClick" 3578 }; 3579 3580 var _oneDelegateHandler = function (id, e, componentType) { 3581 var t = e.srcElement || e.target, 3582 jp = (t && t.parentNode ? t.parentNode._jsPlumb : null) || (t ? t._jsPlumb : null) || (t && t.parentNode && t.parentNode.parentNode ? t.parentNode.parentNode._jsPlumb : null); 3583 if (jp) { 3584 jp.fire(id, jp, e); 3585 var alias = componentType ? eventAliases[componentType + id] || id : id; 3586 // jsplumb also fires every event coming from components/overlays. That's what the test for `jp.component` is for. 3587 _currentInstance.fire(alias, jp.component || jp, e); 3588 } 3589 }; 3590 3591 var _addOneDelegate = function(eventId, selector, fn) { 3592 _containerDelegations.push([eventId, fn]); 3593 _currentInstance.on(_container, eventId, selector, fn); 3594 }; 3595 3596 // delegate one event on the container to jsplumb elements. it might be possible to 3597 // abstract this out: each of endpoint, connection and overlay could register themselves with 3598 // jsplumb as "component types" or whatever, and provide a suitable selector. this would be 3599 // done by the renderer (although admittedly from 2.0 onwards we're not supporting vml anymore) 3600 var _oneDelegate = function (id) { 3601 // connections. 3602 _addOneDelegate(id, ".jtk-connector", function (e) { 3603 _oneDelegateHandler(id, e); 3604 }); 3605 // endpoints. note they can have an enclosing div, or not. 3606 _addOneDelegate(id, ".jtk-endpoint", function (e) { 3607 _oneDelegateHandler(id, e, "endpoint"); 3608 }); 3609 // overlays 3610 _addOneDelegate(id, ".jtk-overlay", function (e) { 3611 _oneDelegateHandler(id, e); 3612 }); 3613 }; 3614 3615 for (var i = 0; i < events.length; i++) { 3616 _oneDelegate(events[i]); 3617 } 3618 3619 // managed elements 3620 for (var elId in managedElements) { 3621 var el = managedElements[elId].el; 3622 if (el.parentNode === previousContainer) { 3623 previousContainer.removeChild(el); 3624 _container.appendChild(el); 3625 } 3626 } 3627 3628 }; 3629 this.getContainer = function () { 3630 return _container; 3631 }; 3632 3633 this.bind = function (event, fn) { 3634 if ("ready" === event && initialized) { 3635 fn(); 3636 } 3637 else { 3638 _bb.apply(_currentInstance, [event, fn]); 3639 } 3640 }; 3641 3642 _currentInstance.importDefaults = function (d) { 3643 for (var i in d) { 3644 _currentInstance.Defaults[i] = d[i]; 3645 } 3646 if (d.Container) { 3647 _currentInstance.setContainer(d.Container); 3648 } 3649 3650 return _currentInstance; 3651 }; 3652 3653 _currentInstance.restoreDefaults = function () { 3654 _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults); 3655 return _currentInstance; 3656 }; 3657 3658 var log = null, 3659 initialized = false, 3660 // TODO remove from window scope 3661 connections = [], 3662 // map of element id -> endpoint lists. an element can have an arbitrary 3663 // number of endpoints on it, and not all of them have to be connected 3664 // to anything. 3665 endpointsByElement = {}, 3666 endpointsByUUID = {}, 3667 managedElements = {}, 3668 offsets = {}, 3669 offsetTimestamps = {}, 3670 draggableStates = {}, 3671 connectionBeingDragged = false, 3672 sizes = [], 3673 _suspendDrawing = false, 3674 _suspendedAt = null, 3675 DEFAULT_SCOPE = this.Defaults.Scope, 3676 _curIdStamp = 1, 3677 _idstamp = function () { 3678 return "" + _curIdStamp++; 3679 }, 3680 3681 // 3682 // appends an element to some other element, which is calculated as follows: 3683 // 3684 // 1. if Container exists, use that element. 3685 // 2. if the 'parent' parameter exists, use that. 3686 // 3. otherwise just use the root element. 3687 // 3688 // 3689 _appendElement = function (el, parent) { 3690 if (_container) { 3691 _container.appendChild(el); 3692 } 3693 else if (!parent) { 3694 this.appendToRoot(el); 3695 } 3696 else { 3697 this.getElement(parent).appendChild(el); 3698 } 3699 }.bind(this), 3700 3701 // 3702 // Draws an endpoint and its connections. this is the main entry point into drawing connections as well 3703 // as endpoints, since jsPlumb is endpoint-centric under the hood. 3704 // 3705 // @param element element to draw (of type library specific element object) 3706 // @param ui UI object from current library's event system. optional. 3707 // @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. 3708 // @param clearEdits defaults to false; indicates that mouse edits for connectors should be cleared 3709 /// 3710 _draw = function (element, ui, timestamp, clearEdits) { 3711 3712 if (!_suspendDrawing) { 3713 var id = _getId(element), 3714 repaintEls, 3715 dm = _currentInstance.getDragManager(); 3716 3717 if (dm) { 3718 repaintEls = dm.getElementsForDraggable(id); 3719 } 3720 3721 if (timestamp == null) { 3722 timestamp = _timestamp(); 3723 } 3724 3725 // update the offset of everything _before_ we try to draw anything. 3726 var o = _updateOffset({ elId: id, offset: ui, recalc: false, timestamp: timestamp }); 3727 3728 if (repaintEls && o && o.o) { 3729 for (var i in repaintEls) { 3730 _updateOffset({ 3731 elId: repaintEls[i].id, 3732 offset: { 3733 left: o.o.left + repaintEls[i].offset.left, 3734 top: o.o.top + repaintEls[i].offset.top 3735 }, 3736 recalc: false, 3737 timestamp: timestamp 3738 }); 3739 } 3740 } 3741 3742 _currentInstance.anchorManager.redraw(id, ui, timestamp, null, clearEdits); 3743 3744 if (repaintEls) { 3745 for (var j in repaintEls) { 3746 _currentInstance.anchorManager.redraw(repaintEls[j].id, ui, timestamp, repaintEls[j].offset, clearEdits, true); 3747 } 3748 } 3749 } 3750 }, 3751 3752 // 3753 // gets an Endpoint by uuid. 3754 // 3755 _getEndpoint = function (uuid) { 3756 return endpointsByUUID[uuid]; 3757 }, 3758 3759 /** 3760 * inits a draggable if it's not already initialised. 3761 * TODO: somehow abstract this to the adapter, because the concept of "draggable" has no 3762 * place on the server. 3763 */ 3764 _initDraggableIfNecessary = function (element, isDraggable, dragOptions, id, fireEvent) { 3765 // move to DragManager? 3766 if (!jsPlumb.headless) { 3767 var _draggable = isDraggable == null ? false : isDraggable; 3768 if (_draggable) { 3769 if (jsPlumb.isDragSupported(element, _currentInstance)) { 3770 var options = dragOptions || _currentInstance.Defaults.DragOptions; 3771 options = jsPlumb.extend({}, options); // make a copy. 3772 if (!jsPlumb.isAlreadyDraggable(element, _currentInstance)) { 3773 var dragEvent = jsPlumb.dragEvents.drag, 3774 stopEvent = jsPlumb.dragEvents.stop, 3775 startEvent = jsPlumb.dragEvents.start, 3776 _started = false; 3777 3778 _manage(id, element); 3779 3780 options[startEvent] = _ju.wrap(options[startEvent], function () { 3781 _currentInstance.setHoverSuspended(true); 3782 _currentInstance.select({source: element}).addClass(_currentInstance.elementDraggingClass + " " + _currentInstance.sourceElementDraggingClass, true); 3783 _currentInstance.select({target: element}).addClass(_currentInstance.elementDraggingClass + " " + _currentInstance.targetElementDraggingClass, true); 3784 _currentInstance.setConnectionBeingDragged(true); 3785 if (options.canDrag) { 3786 return dragOptions.canDrag(); 3787 } 3788 }, false); 3789 3790 options[dragEvent] = _ju.wrap(options[dragEvent], function () { 3791 // TODO: here we could actually use getDragObject, and then compute it ourselves, 3792 // since every adapter does the same thing. but i'm not sure why YUI's getDragObject 3793 // differs from getUIPosition so much 3794 var ui = _currentInstance.getUIPosition(arguments, _currentInstance.getZoom()); 3795 if (ui != null) { 3796 _draw(element, ui, null, true); 3797 if (_started) { 3798 _currentInstance.addClass(element, "jtk-dragged"); 3799 } 3800 _started = true; 3801 } 3802 }); 3803 options[stopEvent] = _ju.wrap(options[stopEvent], function () { 3804 var elements = arguments[0].selection, uip; 3805 3806 var _one = function (_e) { 3807 if (_e[1] != null) { 3808 // run the reported offset through the code that takes parent containers 3809 // into account, to adjust if necessary (issue 554) 3810 uip = _currentInstance.getUIPosition([{ 3811 el:_e[2].el, 3812 pos:[_e[1].left, _e[1].top] 3813 }]); 3814 _draw(_e[2].el, uip); 3815 } 3816 _currentInstance.removeClass(_e[0], "jtk-dragged"); 3817 _currentInstance.select({source: _e[2].el}).removeClass(_currentInstance.elementDraggingClass + " " + _currentInstance.sourceElementDraggingClass, true); 3818 _currentInstance.select({target: _e[2].el}).removeClass(_currentInstance.elementDraggingClass + " " + _currentInstance.targetElementDraggingClass, true); 3819 _currentInstance.getDragManager().dragEnded(_e[2].el); 3820 }; 3821 3822 for (var i = 0; i < elements.length; i++) { 3823 _one(elements[i]); 3824 } 3825 3826 _started = false; 3827 _currentInstance.setHoverSuspended(false); 3828 _currentInstance.setConnectionBeingDragged(false); 3829 }); 3830 var elId = _getId(element); // need ID 3831 draggableStates[elId] = true; 3832 var draggable = draggableStates[elId]; 3833 options.disabled = draggable == null ? false : !draggable; 3834 _currentInstance.initDraggable(element, options); 3835 _currentInstance.getDragManager().register(element); 3836 if (fireEvent) { 3837 _currentInstance.fire("elementDraggable", {el:element, options:options}); 3838 } 3839 } 3840 else { 3841 // already draggable. attach any start, drag or stop listeners to the current Drag. 3842 if (dragOptions.force) { 3843 _currentInstance.initDraggable(element, options); 3844 } 3845 } 3846 } 3847 } 3848 } 3849 }, 3850 3851 _scopeMatch = function (e1, e2) { 3852 var s1 = e1.scope.split(/\s/), s2 = e2.scope.split(/\s/); 3853 for (var i = 0; i < s1.length; i++) { 3854 for (var j = 0; j < s2.length; j++) { 3855 if (s2[j] === s1[i]) { 3856 return true; 3857 } 3858 } 3859 } 3860 3861 return false; 3862 }, 3863 3864 /* 3865 * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. 3866 */ 3867 _prepareConnectionParams = function (params, referenceParams) { 3868 var _p = jsPlumb.extend({ }, params); 3869 if (referenceParams) { 3870 jsPlumb.extend(_p, referenceParams); 3871 } 3872 3873 // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively. 3874 if (_p.source) { 3875 if (_p.source.endpoint) { 3876 _p.sourceEndpoint = _p.source; 3877 } 3878 else { 3879 _p.source = _currentInstance.getElement(_p.source); 3880 } 3881 } 3882 if (_p.target) { 3883 if (_p.target.endpoint) { 3884 _p.targetEndpoint = _p.target; 3885 } 3886 else { 3887 _p.target = _currentInstance.getElement(_p.target); 3888 } 3889 } 3890 3891 // test for endpoint uuids to connect 3892 if (params.uuids) { 3893 _p.sourceEndpoint = _getEndpoint(params.uuids[0]); 3894 _p.targetEndpoint = _getEndpoint(params.uuids[1]); 3895 } 3896 3897 // now ensure that if we do have Endpoints already, they're not full. 3898 // source: 3899 if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { 3900 _ju.log(_currentInstance, "could not add connection; source endpoint is full"); 3901 return; 3902 } 3903 3904 // target: 3905 if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { 3906 _ju.log(_currentInstance, "could not add connection; target endpoint is full"); 3907 return; 3908 } 3909 3910 // if source endpoint mandates connection type and nothing specified in our params, use it. 3911 if (!_p.type && _p.sourceEndpoint) { 3912 _p.type = _p.sourceEndpoint.connectionType; 3913 } 3914 3915 // copy in any connectorOverlays that were specified on the source endpoint. 3916 // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. 3917 if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { 3918 _p.overlays = _p.overlays || []; 3919 for (var i = 0, j = _p.sourceEndpoint.connectorOverlays.length; i < j; i++) { 3920 _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); 3921 } 3922 } 3923 3924 // scope 3925 if (_p.sourceEndpoint && _p.sourceEndpoint.scope) { 3926 _p.scope = _p.sourceEndpoint.scope; 3927 } 3928 3929 // pointer events 3930 if (!_p["pointer-events"] && _p.sourceEndpoint && _p.sourceEndpoint.connectorPointerEvents) { 3931 _p["pointer-events"] = _p.sourceEndpoint.connectorPointerEvents; 3932 } 3933 3934 var _mergeOverrides = function (def, values) { 3935 var m = jsPlumb.extend({}, def); 3936 for (var i in values) { 3937 if (values[i]) { 3938 m[i] = values[i]; 3939 } 3940 } 3941 return m; 3942 }; 3943 3944 var _addEndpoint = function (el, def, idx) { 3945 return _currentInstance.addEndpoint(el, _mergeOverrides(def, { 3946 anchor: _p.anchors ? _p.anchors[idx] : _p.anchor, 3947 endpoint: _p.endpoints ? _p.endpoints[idx] : _p.endpoint, 3948 paintStyle: _p.endpointStyles ? _p.endpointStyles[idx] : _p.endpointStyle, 3949 hoverPaintStyle: _p.endpointHoverStyles ? _p.endpointHoverStyles[idx] : _p.endpointHoverStyle 3950 })); 3951 }; 3952 3953 // check for makeSource/makeTarget specs. 3954 3955 var _oneElementDef = function (type, idx, defs, matchType) { 3956 if (_p[type] && !_p[type].endpoint && !_p[type + "Endpoint"] && !_p.newConnection) { 3957 var tid = _getId(_p[type]), tep = defs[tid]; 3958 3959 tep = tep ? tep[matchType] : null; 3960 3961 if (tep) { 3962 // if not enabled, return. 3963 if (!tep.enabled) { 3964 return false; 3965 } 3966 var newEndpoint = tep.endpoint != null && tep.endpoint._jsPlumb ? tep.endpoint : _addEndpoint(_p[type], tep.def, idx); 3967 if (newEndpoint.isFull()) { 3968 return false; 3969 } 3970 _p[type + "Endpoint"] = newEndpoint; 3971 if (!_p.scope && tep.def.scope) { 3972 _p.scope = tep.def.scope; 3973 } // provide scope if not already provided and endpoint def has one. 3974 newEndpoint.setDeleteOnEmpty(true); 3975 if (tep.uniqueEndpoint) { 3976 if (!tep.endpoint) { 3977 tep.endpoint = newEndpoint; 3978 newEndpoint.setDeleteOnEmpty(false); 3979 } 3980 else { 3981 newEndpoint.finalEndpoint = tep.endpoint; 3982 } 3983 } 3984 } 3985 } 3986 }; 3987 3988 if (_oneElementDef("source", 0, this.sourceEndpointDefinitions, _p.type || "default") === false) { 3989 return; 3990 } 3991 if (_oneElementDef("target", 1, this.targetEndpointDefinitions, _p.type || "default") === false) { 3992 return; 3993 } 3994 3995 // last, ensure scopes match 3996 if (_p.sourceEndpoint && _p.targetEndpoint) { 3997 if (!_scopeMatch(_p.sourceEndpoint, _p.targetEndpoint)) { 3998 _p = null; 3999 } 4000 } 4001 4002 return _p; 4003 }.bind(_currentInstance), 4004 4005 _newConnection = function (params) { 4006 var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(); 4007 4008 params._jsPlumb = _currentInstance; 4009 params.newConnection = _newConnection; 4010 params.newEndpoint = _newEndpoint; 4011 params.endpointsByUUID = endpointsByUUID; 4012 params.endpointsByElement = endpointsByElement; 4013 params.finaliseConnection = _finaliseConnection; 4014 params.id = "con_" + _idstamp(); 4015 var con = new connectionFunc(params); 4016 4017 // if the connection is draggable, then maybe we need to tell the target endpoint to init the 4018 // dragging code. it won't run again if it already configured to be draggable. 4019 if (con.isDetachable()) { 4020 con.endpoints[0].initDraggable("_jsPlumbSource"); 4021 con.endpoints[1].initDraggable("_jsPlumbTarget"); 4022 } 4023 4024 return con; 4025 }, 4026 4027 // 4028 // adds the connection to the backing model, fires an event if necessary and then redraws 4029 // 4030 _finaliseConnection = _currentInstance.finaliseConnection = function (jpc, params, originalEvent, doInformAnchorManager) { 4031 params = params || {}; 4032 // add to list of connections (by scope). 4033 if (!jpc.suspendedEndpoint) { 4034 connections.push(jpc); 4035 } 4036 4037 jpc.pending = null; 4038 4039 // turn off isTemporarySource on the source endpoint (only viable on first draw) 4040 jpc.endpoints[0].isTemporarySource = false; 4041 4042 // always inform the anchor manager 4043 // except that if jpc has a suspended endpoint it's not true to say the 4044 // connection is new; it has just (possibly) moved. the question is whether 4045 // to make that call here or in the anchor manager. i think perhaps here. 4046 if (doInformAnchorManager !== false) { 4047 _currentInstance.anchorManager.newConnection(jpc); 4048 } 4049 4050 // force a paint 4051 _draw(jpc.source); 4052 4053 // fire an event 4054 if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { 4055 4056 var eventArgs = { 4057 connection: jpc, 4058 source: jpc.source, target: jpc.target, 4059 sourceId: jpc.sourceId, targetId: jpc.targetId, 4060 sourceEndpoint: jpc.endpoints[0], targetEndpoint: jpc.endpoints[1] 4061 }; 4062 4063 _currentInstance.fire("connection", eventArgs, originalEvent); 4064 } 4065 }, 4066 4067 /* 4068 factory method to prepare a new endpoint. this should always be used instead of creating Endpoints 4069 manually, since this method attaches event listeners and an id. 4070 */ 4071 _newEndpoint = function (params, id) { 4072 var endpointFunc = _currentInstance.Defaults.EndpointType || jsPlumb.Endpoint; 4073 var _p = jsPlumb.extend({}, params); 4074 _p._jsPlumb = _currentInstance; 4075 _p.newConnection = _newConnection; 4076 _p.newEndpoint = _newEndpoint; 4077 _p.endpointsByUUID = endpointsByUUID; 4078 _p.endpointsByElement = endpointsByElement; 4079 _p.fireDetachEvent = fireDetachEvent; 4080 _p.elementId = id || _getId(_p.source); 4081 var ep = new endpointFunc(_p); 4082 ep.id = "ep_" + _idstamp(); 4083 _manage(_p.elementId, _p.source); 4084 4085 if (!jsPlumb.headless) { 4086 _currentInstance.getDragManager().endpointAdded(_p.source, id); 4087 } 4088 4089 return ep; 4090 }, 4091 4092 /* 4093 * performs the given function operation on all the connections found 4094 * for the given element id; this means we find all the endpoints for 4095 * the given element, and then for each endpoint find the connectors 4096 * connected to it. then we pass each connection in to the given 4097 * function. 4098 */ 4099 _operation = function (elId, func, endpointFunc) { 4100 var endpoints = endpointsByElement[elId]; 4101 if (endpoints && endpoints.length) { 4102 for (var i = 0, ii = endpoints.length; i < ii; i++) { 4103 for (var j = 0, jj = endpoints[i].connections.length; j < jj; j++) { 4104 var retVal = func(endpoints[i].connections[j]); 4105 // if the function passed in returns true, we exit. 4106 // most functions return false. 4107 if (retVal) { 4108 return; 4109 } 4110 } 4111 if (endpointFunc) { 4112 endpointFunc(endpoints[i]); 4113 } 4114 } 4115 } 4116 }, 4117 4118 _setDraggable = function (element, draggable) { 4119 return jsPlumb.each(element, function (el) { 4120 if (_currentInstance.isDragSupported(el)) { 4121 draggableStates[_currentInstance.getAttribute(el, "id")] = draggable; 4122 _currentInstance.setElementDraggable(el, draggable); 4123 } 4124 }); 4125 }, 4126 /* 4127 * private method to do the business of hiding/showing. 4128 * 4129 * @param el 4130 * either Id of the element in question or a library specific 4131 * object for the element. 4132 * @param state 4133 * String specifying a value for the css 'display' property 4134 * ('block' or 'none'). 4135 */ 4136 _setVisible = function (el, state, alsoChangeEndpoints) { 4137 state = state === "block"; 4138 var endpointFunc = null; 4139 if (alsoChangeEndpoints) { 4140 endpointFunc = function (ep) { 4141 ep.setVisible(state, true, true); 4142 }; 4143 } 4144 var info = _info(el); 4145 _operation(info.id, function (jpc) { 4146 if (state && alsoChangeEndpoints) { 4147 // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. 4148 // this block will only set a connection to be visible if the other endpoint in the connection is also visible. 4149 var oidx = jpc.sourceId === info.id ? 1 : 0; 4150 if (jpc.endpoints[oidx].isVisible()) { 4151 jpc.setVisible(true); 4152 } 4153 } 4154 else { // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. 4155 jpc.setVisible(state); 4156 } 4157 }, endpointFunc); 4158 }, 4159 /* 4160 * toggles the draggable state of the given element(s). 4161 * el is either an id, or an element object, or a list of ids/element objects. 4162 */ 4163 _toggleDraggable = function (el) { 4164 var state; 4165 jsPlumb.each(el, function (el) { 4166 var elId = _currentInstance.getAttribute(el, "id"); 4167 state = draggableStates[elId] == null ? false : draggableStates[elId]; 4168 state = !state; 4169 draggableStates[elId] = state; 4170 _currentInstance.setDraggable(el, state); 4171 return state; 4172 }.bind(this)); 4173 return state; 4174 }, 4175 /** 4176 * private method to do the business of toggling hiding/showing. 4177 */ 4178 _toggleVisible = function (elId, changeEndpoints) { 4179 var endpointFunc = null; 4180 if (changeEndpoints) { 4181 endpointFunc = function (ep) { 4182 var state = ep.isVisible(); 4183 ep.setVisible(!state); 4184 }; 4185 } 4186 _operation(elId, function (jpc) { 4187 var state = jpc.isVisible(); 4188 jpc.setVisible(!state); 4189 }, endpointFunc); 4190 }, 4191 4192 // TODO comparison performance 4193 _getCachedData = function (elId) { 4194 var o = offsets[elId]; 4195 if (!o) { 4196 return _updateOffset({elId: elId}); 4197 } 4198 else { 4199 return {o: o, s: sizes[elId]}; 4200 } 4201 }, 4202 4203 /** 4204 * gets an id for the given element, creating and setting one if 4205 * necessary. the id is of the form 4206 * 4207 * jsPlumb_<instance index>_<index in instance> 4208 * 4209 * where "index in instance" is a monotonically increasing integer that starts at 0, 4210 * for each instance. this method is used not only to assign ids to elements that do not 4211 * have them but also to connections and endpoints. 4212 */ 4213 _getId = function (element, uuid, doNotCreateIfNotFound) { 4214 if (_ju.isString(element)) { 4215 return element; 4216 } 4217 if (element == null) { 4218 return null; 4219 } 4220 var id = _currentInstance.getAttribute(element, "id"); 4221 if (!id || id === "undefined") { 4222 // check if fixed uuid parameter is given 4223 if (arguments.length === 2 && arguments[1] !== undefined) { 4224 id = uuid; 4225 } 4226 else if (arguments.length === 1 || (arguments.length === 3 && !arguments[2])) { 4227 id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); 4228 } 4229 4230 if (!doNotCreateIfNotFound) { 4231 _currentInstance.setAttribute(element, "id", id); 4232 } 4233 } 4234 return id; 4235 }; 4236 4237 this.setConnectionBeingDragged = function (v) { 4238 connectionBeingDragged = v; 4239 }; 4240 this.isConnectionBeingDragged = function () { 4241 return connectionBeingDragged; 4242 }; 4243 4244 /** 4245 * Returns a map of all the elements this jsPlumbInstance is currently managing. 4246 * @returns {Object} Map of [id-> {el, endpoint[], connection, position}] information. 4247 */ 4248 this.getManagedElements = function() { 4249 return managedElements; 4250 }; 4251 4252 this.connectorClass = "jtk-connector"; 4253 this.connectorOutlineClass = "jtk-connector-outline"; 4254 this.editableConnectorClass = "jtk-connector-editable"; 4255 this.connectedClass = "jtk-connected"; 4256 this.hoverClass = "jtk-hover"; 4257 this.endpointClass = "jtk-endpoint"; 4258 this.endpointConnectedClass = "jtk-endpoint-connected"; 4259 this.endpointFullClass = "jtk-endpoint-full"; 4260 this.endpointDropAllowedClass = "jtk-endpoint-drop-allowed"; 4261 this.endpointDropForbiddenClass = "jtk-endpoint-drop-forbidden"; 4262 this.overlayClass = "jtk-overlay"; 4263 this.draggingClass = "jtk-dragging"; 4264 this.elementDraggingClass = "jtk-element-dragging"; 4265 this.sourceElementDraggingClass = "jtk-source-element-dragging"; 4266 this.targetElementDraggingClass = "jtk-target-element-dragging"; 4267 this.endpointAnchorClassPrefix = "jtk-endpoint-anchor"; 4268 this.hoverSourceClass = "jtk-source-hover"; 4269 this.hoverTargetClass = "jtk-target-hover"; 4270 this.dragSelectClass = "jtk-drag-select"; 4271 4272 this.Anchors = {}; 4273 this.Connectors = { "svg": {} }; 4274 this.Endpoints = { "svg": {} }; 4275 this.Overlays = { "svg": {} } ; 4276 this.ConnectorRenderers = {}; 4277 this.SVG = "svg"; 4278 4279 // --------------------------- jsPlumbInstance public API --------------------------------------------------------- 4280 4281 4282 this.addEndpoint = function (el, params, referenceParams) { 4283 referenceParams = referenceParams || {}; 4284 var p = jsPlumb.extend({}, referenceParams); 4285 jsPlumb.extend(p, params); 4286 p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint; 4287 p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle; 4288 4289 var results = [], 4290 inputs = (_ju.isArray(el) || (el.length != null && !_ju.isString(el))) ? el : [ el ]; 4291 4292 for (var i = 0, j = inputs.length; i < j; i++) { 4293 p.source = _currentInstance.getElement(inputs[i]); 4294 _ensureContainer(p.source); 4295 4296 var id = _getId(p.source), e = _newEndpoint(p, id); 4297 4298 // ensure element is managed. 4299 var myOffset = _manage(id, p.source).info.o; 4300 _ju.addToList(endpointsByElement, id, e); 4301 4302 if (!_suspendDrawing) { 4303 e.paint({ 4304 anchorLoc: e.anchor.compute({ xy: [ myOffset.left, myOffset.top ], wh: sizes[id], element: e, timestamp: _suspendedAt }), 4305 timestamp: _suspendedAt 4306 }); 4307 } 4308 4309 results.push(e); 4310 } 4311 4312 return results.length === 1 ? results[0] : results; 4313 }; 4314 4315 this.addEndpoints = function (el, endpoints, referenceParams) { 4316 var results = []; 4317 for (var i = 0, j = endpoints.length; i < j; i++) { 4318 var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); 4319 if (_ju.isArray(e)) { 4320 Array.prototype.push.apply(results, e); 4321 } 4322 else { 4323 results.push(e); 4324 } 4325 } 4326 return results; 4327 }; 4328 4329 this.animate = function (el, properties, options) { 4330 if (!this.animationSupported) { 4331 return false; 4332 } 4333 4334 options = options || {}; 4335 var del = _currentInstance.getElement(el), 4336 id = _getId(del), 4337 stepFunction = jsPlumb.animEvents.step, 4338 completeFunction = jsPlumb.animEvents.complete; 4339 4340 options[stepFunction] = _ju.wrap(options[stepFunction], function () { 4341 _currentInstance.revalidate(id); 4342 }); 4343 4344 // onComplete repaints, just to make sure everything looks good at the end of the animation. 4345 options[completeFunction] = _ju.wrap(options[completeFunction], function () { 4346 _currentInstance.revalidate(id); 4347 }); 4348 4349 _currentInstance.doAnimate(del, properties, options); 4350 }; 4351 4352 /** 4353 * checks for a listener for the given condition, executing it if found, passing in the given value. 4354 * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since 4355 * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" 4356 * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these 4357 * condition events anyway. 4358 */ 4359 this.checkCondition = function (conditionName, args) { 4360 var l = _currentInstance.getListener(conditionName), 4361 r = true; 4362 4363 if (l && l.length > 0) { 4364 var values = Array.prototype.slice.call(arguments, 1); 4365 try { 4366 for (var i = 0, j = l.length; i < j; i++) { 4367 r = r && l[i].apply(l[i], values); 4368 } 4369 } 4370 catch (e) { 4371 _ju.log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); 4372 } 4373 } 4374 return r; 4375 }; 4376 4377 this.connect = function (params, referenceParams) { 4378 // prepare a final set of parameters to create connection with 4379 var _p = _prepareConnectionParams(params, referenceParams), jpc; 4380 // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams 4381 // will return null (and log something) if either endpoint was full. what would be nicer is to 4382 // create a dedicated 'error' object. 4383 if (_p) { 4384 if (_p.source == null && _p.sourceEndpoint == null) { 4385 _ju.log("Cannot establish connection - source does not exist"); 4386 return; 4387 } 4388 if (_p.target == null && _p.targetEndpoint == null) { 4389 _ju.log("Cannot establish connection - target does not exist"); 4390 return; 4391 } 4392 _ensureContainer(_p.source); 4393 // create the connection. it is not yet registered 4394 jpc = _newConnection(_p); 4395 // now add it the model, fire an event, and redraw 4396 _finaliseConnection(jpc, _p); 4397 } 4398 return jpc; 4399 }; 4400 4401 var stTypes = [ 4402 { el: "source", elId: "sourceId", epDefs: "sourceEndpointDefinitions" }, 4403 { el: "target", elId: "targetId", epDefs: "targetEndpointDefinitions" } 4404 ]; 4405 4406 var _set = function (c, el, idx, doNotRepaint) { 4407 var ep, _st = stTypes[idx], cId = c[_st.elId], cEl = c[_st.el], sid, sep, 4408 oldEndpoint = c.endpoints[idx]; 4409 4410 var evtParams = { 4411 index: idx, 4412 originalSourceId: idx === 0 ? cId : c.sourceId, 4413 newSourceId: c.sourceId, 4414 originalTargetId: idx === 1 ? cId : c.targetId, 4415 newTargetId: c.targetId, 4416 connection: c 4417 }; 4418 4419 if (el.constructor === jsPlumb.Endpoint) { 4420 ep = el; 4421 ep.addConnection(c); 4422 el = ep.element; 4423 } 4424 else { 4425 sid = _getId(el); 4426 sep = this[_st.epDefs][sid]; 4427 4428 if (sid === c[_st.elId]) { 4429 ep = null; // dont change source/target if the element is already the one given. 4430 } 4431 else if (sep) { 4432 for (var t in sep) { 4433 if (!sep[t].enabled) { 4434 return; 4435 } 4436 ep = sep[t].endpoint != null && sep[t].endpoint._jsPlumb ? sep[t].endpoint : this.addEndpoint(el, sep[t].def); 4437 if (sep[t].uniqueEndpoint) { 4438 sep[t].endpoint = ep; 4439 } 4440 ep.addConnection(c); 4441 } 4442 } 4443 else { 4444 ep = c.makeEndpoint(idx === 0, el, sid); 4445 } 4446 } 4447 4448 if (ep != null) { 4449 oldEndpoint.detachFromConnection(c); 4450 c.endpoints[idx] = ep; 4451 c[_st.el] = ep.element; 4452 c[_st.elId] = ep.elementId; 4453 evtParams[idx === 0 ? "newSourceId" : "newTargetId"] = ep.elementId; 4454 4455 fireMoveEvent(evtParams); 4456 4457 if (!doNotRepaint) { 4458 c.repaint(); 4459 } 4460 } 4461 4462 evtParams.element = el; 4463 return evtParams; 4464 4465 }.bind(this); 4466 4467 this.setSource = function (connection, el, doNotRepaint) { 4468 var p = _set(connection, el, 0, doNotRepaint); 4469 this.anchorManager.sourceChanged(p.originalSourceId, p.newSourceId, connection, p.el); 4470 }; 4471 this.setTarget = function (connection, el, doNotRepaint) { 4472 var p = _set(connection, el, 1, doNotRepaint); 4473 this.anchorManager.updateOtherEndpoint(p.originalSourceId, p.originalTargetId, p.newTargetId, connection); 4474 }; 4475 4476 this.deleteEndpoint = function (object, dontUpdateHover, deleteAttachedObjects) { 4477 var endpoint = (typeof object === "string") ? endpointsByUUID[object] : object; 4478 if (endpoint) { 4479 _currentInstance.deleteObject({ endpoint: endpoint, dontUpdateHover: dontUpdateHover, deleteAttachedObjects:deleteAttachedObjects }); 4480 } 4481 return _currentInstance; 4482 }; 4483 4484 this.deleteEveryEndpoint = function () { 4485 var _is = _currentInstance.setSuspendDrawing(true); 4486 for (var id in endpointsByElement) { 4487 var endpoints = endpointsByElement[id]; 4488 if (endpoints && endpoints.length) { 4489 for (var i = 0, j = endpoints.length; i < j; i++) { 4490 _currentInstance.deleteEndpoint(endpoints[i], true); 4491 } 4492 } 4493 } 4494 endpointsByElement = {}; 4495 managedElements = {}; 4496 endpointsByUUID = {}; 4497 offsets = {}; 4498 offsetTimestamps = {}; 4499 _currentInstance.anchorManager.reset(); 4500 var dm = _currentInstance.getDragManager(); 4501 if (dm) { 4502 dm.reset(); 4503 } 4504 if (!_is) { 4505 _currentInstance.setSuspendDrawing(false); 4506 } 4507 return _currentInstance; 4508 }; 4509 4510 var fireDetachEvent = function (jpc, doFireEvent, originalEvent) { 4511 // may have been given a connection, or in special cases, an object 4512 var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), 4513 argIsConnection = jpc.constructor === connType, 4514 params = argIsConnection ? { 4515 connection: jpc, 4516 source: jpc.source, target: jpc.target, 4517 sourceId: jpc.sourceId, targetId: jpc.targetId, 4518 sourceEndpoint: jpc.endpoints[0], targetEndpoint: jpc.endpoints[1] 4519 } : jpc; 4520 4521 if (doFireEvent) { 4522 _currentInstance.fire("connectionDetached", params, originalEvent); 4523 } 4524 4525 // always fire this. used by internal jsplumb stuff. 4526 _currentInstance.fire("internal.connectionDetached", params, originalEvent); 4527 4528 _currentInstance.anchorManager.connectionDetached(params); 4529 }; 4530 4531 var fireMoveEvent = _currentInstance.fireMoveEvent = function (params, evt) { 4532 _currentInstance.fire("connectionMoved", params, evt); 4533 }; 4534 4535 this.unregisterEndpoint = function (endpoint) { 4536 if (endpoint._jsPlumb.uuid) { 4537 endpointsByUUID[endpoint._jsPlumb.uuid] = null; 4538 } 4539 _currentInstance.anchorManager.deleteEndpoint(endpoint); 4540 // TODO at least replace this with a removeWithFunction call. 4541 for (var e in endpointsByElement) { 4542 var endpoints = endpointsByElement[e]; 4543 if (endpoints) { 4544 var newEndpoints = []; 4545 for (var i = 0, j = endpoints.length; i < j; i++) { 4546 if (endpoints[i] !== endpoint) { 4547 newEndpoints.push(endpoints[i]); 4548 } 4549 } 4550 4551 endpointsByElement[e] = newEndpoints; 4552 } 4553 if (endpointsByElement[e].length < 1) { 4554 delete endpointsByElement[e]; 4555 } 4556 } 4557 }; 4558 4559 var IS_DETACH_ALLOWED = "isDetachAllowed"; 4560 var BEFORE_DETACH = "beforeDetach"; 4561 var CHECK_CONDITION = "checkCondition"; 4562 4563 /** 4564 * Deletes a Connection. 4565 * @method deleteConnection 4566 * @param connection Connection to delete 4567 * @param {Object} [params] Optional delete parameters 4568 * @param {Boolean} [params.doNotFireEvent=false] If true, a connection detached event will not be fired. Otherwise one will. 4569 * @param {Boolean} [params.force=false] If true, the connection will be deleted even if a beforeDetach interceptor tries to stop the deletion. 4570 * @returns {Boolean} True if the connection was deleted, false otherwise. 4571 */ 4572 this.deleteConnection = function(connection, params) { 4573 4574 if (connection != null) { 4575 params = params || {}; 4576 4577 if (params.force || _ju.functionChain(true, false, [ 4578 [ connection.endpoints[0], IS_DETACH_ALLOWED, [ connection ] ], 4579 [ connection.endpoints[1], IS_DETACH_ALLOWED, [ connection ] ], 4580 [ connection, IS_DETACH_ALLOWED, [ connection ] ], 4581 [ _currentInstance, CHECK_CONDITION, [ BEFORE_DETACH, connection ] ] 4582 ])) { 4583 4584 connection.setHover(false); 4585 fireDetachEvent(connection, !connection.pending && params.fireEvent !== false, params.originalEvent); 4586 4587 connection.endpoints[0].detachFromConnection(connection); 4588 connection.endpoints[1].detachFromConnection(connection); 4589 _ju.removeWithFunction(connections, function (_c) { 4590 return connection.id === _c.id; 4591 }); 4592 4593 connection.cleanup(); 4594 connection.destroy(); 4595 return true; 4596 } 4597 } 4598 return false; 4599 }; 4600 4601 /** 4602 * Remove all Connections from all elements, but leaves Endpoints in place ((unless a connection is set to auto delete its Endpoints). 4603 * @method deleteEveryConnection 4604 * @param {Object} [params] optional params object for the call 4605 * @param {Boolean} [params.fireEvent=true] Whether or not to fire detach events 4606 * @param {Boolean} [params.forceDetach=false] If true, this call will ignore any `beforeDetach` interceptors. 4607 * @returns {Number} The number of connections that were deleted. 4608 */ 4609 this.deleteEveryConnection = function (params) { 4610 params = params || {}; 4611 var count = connections.length, deletedCount = 0; 4612 _currentInstance.batch(function () { 4613 for (var i = 0; i < count; i++) { 4614 deletedCount += _currentInstance.deleteConnection(connections[0], params) ? 1 : 0; 4615 } 4616 }); 4617 return deletedCount; 4618 }; 4619 4620 /** 4621 * Removes all an element's Connections. 4622 * @method deleteConnectionsForElement 4623 * @param {Object} el Either the id of the element, or a selector for the element. 4624 * @param {Object} [params] Optional parameters. 4625 * @param {Boolean} [params.fireEvent=true] Whether or not to fire the detach event. 4626 * @param {Boolean} [params.forceDetach=false] If true, this call will ignore any `beforeDetach` interceptors. 4627 * @return {jsPlumbInstance} The current jsPlumb instance. 4628 */ 4629 this.deleteConnectionsForElement = function (el, params) { 4630 params = params || {}; 4631 el = _currentInstance.getElement(el); 4632 var id = _getId(el), endpoints = endpointsByElement[id]; 4633 if (endpoints && endpoints.length) { 4634 for (var i = 0, j = endpoints.length; i < j; i++) { 4635 endpoints[i].deleteEveryConnection(params); 4636 } 4637 } 4638 return _currentInstance; 4639 }; 4640 4641 /// not public. but of course its exposed. how to change this. 4642 this.deleteObject = function (params) { 4643 var result = { 4644 endpoints: {}, 4645 connections: {}, 4646 endpointCount: 0, 4647 connectionCount: 0 4648 }, 4649 deleteAttachedObjects = params.deleteAttachedObjects !== false; 4650 4651 var unravelConnection = function (connection) { 4652 if (connection != null && result.connections[connection.id] == null) { 4653 if (!params.dontUpdateHover && connection._jsPlumb != null) { 4654 connection.setHover(false); 4655 } 4656 result.connections[connection.id] = connection; 4657 result.connectionCount++; 4658 } 4659 }; 4660 var unravelEndpoint = function (endpoint) { 4661 if (endpoint != null && result.endpoints[endpoint.id] == null) { 4662 if (!params.dontUpdateHover && endpoint._jsPlumb != null) { 4663 endpoint.setHover(false); 4664 } 4665 result.endpoints[endpoint.id] = endpoint; 4666 result.endpointCount++; 4667 4668 if (deleteAttachedObjects) { 4669 for (var i = 0; i < endpoint.connections.length; i++) { 4670 var c = endpoint.connections[i]; 4671 unravelConnection(c); 4672 } 4673 } 4674 } 4675 }; 4676 4677 if (params.connection) { 4678 unravelConnection(params.connection); 4679 } 4680 else { 4681 unravelEndpoint(params.endpoint); 4682 } 4683 4684 // loop through connections 4685 for (var i in result.connections) { 4686 var c = result.connections[i]; 4687 if (c._jsPlumb) { 4688 _ju.removeWithFunction(connections, function (_c) { 4689 return c.id === _c.id; 4690 }); 4691 4692 fireDetachEvent(c, params.fireEvent === false ? false : !c.pending, params.originalEvent); 4693 var doNotCleanup = params.deleteAttachedObjects == null ? null : !params.deleteAttachedObjects; 4694 4695 c.endpoints[0].detachFromConnection(c, null, doNotCleanup); 4696 c.endpoints[1].detachFromConnection(c, null, doNotCleanup); 4697 4698 c.cleanup(true); 4699 c.destroy(true); 4700 } 4701 } 4702 4703 // loop through endpoints 4704 for (var j in result.endpoints) { 4705 var e = result.endpoints[j]; 4706 if (e._jsPlumb) { 4707 _currentInstance.unregisterEndpoint(e); 4708 // FIRE some endpoint deleted event? 4709 e.cleanup(true); 4710 e.destroy(true); 4711 } 4712 } 4713 4714 return result; 4715 }; 4716 4717 this.draggable = function (el, options) { 4718 var info; 4719 _each(function(_el) { 4720 info = _info(_el); 4721 if (info.el) { 4722 _initDraggableIfNecessary(info.el, true, options, info.id, true); 4723 } 4724 }, el); 4725 return _currentInstance; 4726 }; 4727 4728 this.droppable = function(el, options) { 4729 var info; 4730 options = options || {}; 4731 options.allowLoopback = false; 4732 _each(function(_el) { 4733 info = _info(_el); 4734 if (info.el) { 4735 _currentInstance.initDroppable(info.el, options); 4736 } 4737 }, el); 4738 return _currentInstance; 4739 }; 4740 4741 // helpers for select/selectEndpoints 4742 var _setOperation = function (list, func, args, selector) { 4743 for (var i = 0, j = list.length; i < j; i++) { 4744 list[i][func].apply(list[i], args); 4745 } 4746 return selector(list); 4747 }, 4748 _getOperation = function (list, func, args) { 4749 var out = []; 4750 for (var i = 0, j = list.length; i < j; i++) { 4751 out.push([ list[i][func].apply(list[i], args), list[i] ]); 4752 } 4753 return out; 4754 }, 4755 setter = function (list, func, selector) { 4756 return function () { 4757 return _setOperation(list, func, arguments, selector); 4758 }; 4759 }, 4760 getter = function (list, func) { 4761 return function () { 4762 return _getOperation(list, func, arguments); 4763 }; 4764 }, 4765 prepareList = function (input, doNotGetIds) { 4766 var r = []; 4767 if (input) { 4768 if (typeof input === 'string') { 4769 if (input === "*") { 4770 return input; 4771 } 4772 r.push(input); 4773 } 4774 else { 4775 if (doNotGetIds) { 4776 r = input; 4777 } 4778 else { 4779 if (input.length) { 4780 for (var i = 0, j = input.length; i < j; i++) { 4781 r.push(_info(input[i]).id); 4782 } 4783 } 4784 else { 4785 r.push(_info(input).id); 4786 } 4787 } 4788 } 4789 } 4790 return r; 4791 }, 4792 filterList = function (list, value, missingIsFalse) { 4793 if (list === "*") { 4794 return true; 4795 } 4796 return list.length > 0 ? list.indexOf(value) !== -1 : !missingIsFalse; 4797 }; 4798 4799 // get some connections, specifying source/target/scope 4800 this.getConnections = function (options, flat) { 4801 if (!options) { 4802 options = {}; 4803 } else if (options.constructor === String) { 4804 options = { "scope": options }; 4805 } 4806 var scope = options.scope || _currentInstance.getDefaultScope(), 4807 scopes = prepareList(scope, true), 4808 sources = prepareList(options.source), 4809 targets = prepareList(options.target), 4810 results = (!flat && scopes.length > 1) ? {} : [], 4811 _addOne = function (scope, obj) { 4812 if (!flat && scopes.length > 1) { 4813 var ss = results[scope]; 4814 if (ss == null) { 4815 ss = results[scope] = []; 4816 } 4817 ss.push(obj); 4818 } else { 4819 results.push(obj); 4820 } 4821 }; 4822 4823 for (var j = 0, jj = connections.length; j < jj; j++) { 4824 var c = connections[j], 4825 sourceId = c.proxies && c.proxies[0] ? c.proxies[0].originalEp.elementId : c.sourceId, 4826 targetId = c.proxies && c.proxies[1] ? c.proxies[1].originalEp.elementId : c.targetId; 4827 4828 if (filterList(scopes, c.scope) && filterList(sources, sourceId) && filterList(targets, targetId)) { 4829 _addOne(c.scope, c); 4830 } 4831 } 4832 4833 return results; 4834 }; 4835 4836 var _curryEach = function (list, executor) { 4837 return function (f) { 4838 for (var i = 0, ii = list.length; i < ii; i++) { 4839 f(list[i]); 4840 } 4841 return executor(list); 4842 }; 4843 }, 4844 _curryGet = function (list) { 4845 return function (idx) { 4846 return list[idx]; 4847 }; 4848 }; 4849 4850 var _makeCommonSelectHandler = function (list, executor) { 4851 var out = { 4852 length: list.length, 4853 each: _curryEach(list, executor), 4854 get: _curryGet(list) 4855 }, 4856 setters = ["setHover", "removeAllOverlays", "setLabel", "addClass", "addOverlay", "removeOverlay", 4857 "removeOverlays", "showOverlay", "hideOverlay", "showOverlays", "hideOverlays", "setPaintStyle", 4858 "setHoverPaintStyle", "setSuspendEvents", "setParameter", "setParameters", "setVisible", 4859 "repaint", "addType", "toggleType", "removeType", "removeClass", "setType", "bind", "unbind" ], 4860 4861 getters = ["getLabel", "getOverlay", "isHover", "getParameter", "getParameters", "getPaintStyle", 4862 "getHoverPaintStyle", "isVisible", "hasType", "getType", "isSuspendEvents" ], 4863 i, ii; 4864 4865 for (i = 0, ii = setters.length; i < ii; i++) { 4866 out[setters[i]] = setter(list, setters[i], executor); 4867 } 4868 4869 for (i = 0, ii = getters.length; i < ii; i++) { 4870 out[getters[i]] = getter(list, getters[i]); 4871 } 4872 4873 return out; 4874 }; 4875 4876 var _makeConnectionSelectHandler = function (list) { 4877 var common = _makeCommonSelectHandler(list, _makeConnectionSelectHandler); 4878 return jsPlumb.extend(common, { 4879 // setters 4880 setDetachable: setter(list, "setDetachable", _makeConnectionSelectHandler), 4881 setReattach: setter(list, "setReattach", _makeConnectionSelectHandler), 4882 setConnector: setter(list, "setConnector", _makeConnectionSelectHandler), 4883 delete: function () { 4884 for (var i = 0, ii = list.length; i < ii; i++) { 4885 _currentInstance.deleteConnection(list[i]); 4886 } 4887 }, 4888 // getters 4889 isDetachable: getter(list, "isDetachable"), 4890 isReattach: getter(list, "isReattach") 4891 }); 4892 }; 4893 4894 var _makeEndpointSelectHandler = function (list) { 4895 var common = _makeCommonSelectHandler(list, _makeEndpointSelectHandler); 4896 return jsPlumb.extend(common, { 4897 setEnabled: setter(list, "setEnabled", _makeEndpointSelectHandler), 4898 setAnchor: setter(list, "setAnchor", _makeEndpointSelectHandler), 4899 isEnabled: getter(list, "isEnabled"), 4900 deleteEveryConnection: function () { 4901 for (var i = 0, ii = list.length; i < ii; i++) { 4902 list[i].deleteEveryConnection(); 4903 } 4904 }, 4905 "delete": function () { 4906 for (var i = 0, ii = list.length; i < ii; i++) { 4907 _currentInstance.deleteEndpoint(list[i]); 4908 } 4909 } 4910 }); 4911 }; 4912 4913 this.select = function (params) { 4914 params = params || {}; 4915 params.scope = params.scope || "*"; 4916 return _makeConnectionSelectHandler(params.connections || _currentInstance.getConnections(params, true)); 4917 }; 4918 4919 this.selectEndpoints = function (params) { 4920 params = params || {}; 4921 params.scope = params.scope || "*"; 4922 var noElementFilters = !params.element && !params.source && !params.target, 4923 elements = noElementFilters ? "*" : prepareList(params.element), 4924 sources = noElementFilters ? "*" : prepareList(params.source), 4925 targets = noElementFilters ? "*" : prepareList(params.target), 4926 scopes = prepareList(params.scope, true); 4927 4928 var ep = []; 4929 4930 for (var el in endpointsByElement) { 4931 var either = filterList(elements, el, true), 4932 source = filterList(sources, el, true), 4933 sourceMatchExact = sources !== "*", 4934 target = filterList(targets, el, true), 4935 targetMatchExact = targets !== "*"; 4936 4937 // if they requested 'either' then just match scope. otherwise if they requested 'source' (not as a wildcard) then we have to match only endpoints that have isSource set to to true, and the same thing with isTarget. 4938 if (either || source || target) { 4939 inner: 4940 for (var i = 0, ii = endpointsByElement[el].length; i < ii; i++) { 4941 var _ep = endpointsByElement[el][i]; 4942 if (filterList(scopes, _ep.scope, true)) { 4943 4944 var noMatchSource = (sourceMatchExact && sources.length > 0 && !_ep.isSource), 4945 noMatchTarget = (targetMatchExact && targets.length > 0 && !_ep.isTarget); 4946 4947 if (noMatchSource || noMatchTarget) { 4948 continue inner; 4949 } 4950 4951 ep.push(_ep); 4952 } 4953 } 4954 } 4955 } 4956 4957 return _makeEndpointSelectHandler(ep); 4958 }; 4959 4960 // get all connections managed by the instance of jsplumb. 4961 this.getAllConnections = function () { 4962 return connections; 4963 }; 4964 this.getDefaultScope = function () { 4965 return DEFAULT_SCOPE; 4966 }; 4967 // get an endpoint by uuid. 4968 this.getEndpoint = _getEndpoint; 4969 /** 4970 * Gets the list of Endpoints for a given element. 4971 * @method getEndpoints 4972 * @param {String|Element|Selector} el The element to get endpoints for. 4973 * @return {Endpoint[]} An array of Endpoints for the specified element. 4974 */ 4975 this.getEndpoints = function (el) { 4976 return endpointsByElement[_info(el).id] || []; 4977 }; 4978 // gets the default endpoint type. used when subclassing. see wiki. 4979 this.getDefaultEndpointType = function () { 4980 return jsPlumb.Endpoint; 4981 }; 4982 // gets the default connection type. used when subclassing. see wiki. 4983 this.getDefaultConnectionType = function () { 4984 return jsPlumb.Connection; 4985 }; 4986 /* 4987 * Gets an element's id, creating one if necessary. really only exposed 4988 * for the lib-specific functionality to access; would be better to pass 4989 * the current instance into the lib-specific code (even though this is 4990 * a static call. i just don't want to expose it to the public API). 4991 */ 4992 this.getId = _getId; 4993 4994 this.appendElement = _appendElement; 4995 4996 var _hoverSuspended = false; 4997 this.isHoverSuspended = function () { 4998 return _hoverSuspended; 4999 }; 5000 this.setHoverSuspended = function (s) { 5001 _hoverSuspended = s; 5002 }; 5003 5004 // set an element's connections to be hidden 5005 this.hide = function (el, changeEndpoints) { 5006 _setVisible(el, "none", changeEndpoints); 5007 return _currentInstance; 5008 }; 5009 5010 // exposed for other objects to use to get a unique id. 5011 this.idstamp = _idstamp; 5012 5013 this.connectorsInitialized = false; 5014 this.registerConnectorType = function (connector, name) { 5015 connectorTypes.push([connector, name]); 5016 }; 5017 5018 // ensure that, if the current container exists, it is a DOM element and not a selector. 5019 // if it does not exist and `candidate` is supplied, the offset parent of that element will be set as the Container. 5020 // this is used to do a better default behaviour for the case that the user has not set a container: 5021 // addEndpoint, makeSource, makeTarget and connect all call this method with the offsetParent of the 5022 // element in question (for connect it is the source element). So if no container is set, it is inferred 5023 // to be the offsetParent of the first element the user tries to connect. 5024 var _ensureContainer = function (candidate) { 5025 if (!_container && candidate) { 5026 var can = _currentInstance.getElement(candidate); 5027 if (can.offsetParent) { 5028 _currentInstance.setContainer(can.offsetParent); 5029 } 5030 } 5031 }; 5032 5033 var _getContainerFromDefaults = function () { 5034 if (_currentInstance.Defaults.Container) { 5035 _currentInstance.setContainer(_currentInstance.Defaults.Container); 5036 } 5037 }; 5038 5039 // check if a given element is managed or not. if not, add to our map. if drawing is not suspended then 5040 // we'll also stash its dimensions; otherwise we'll do this in a lazy way through updateOffset. 5041 var _manage = _currentInstance.manage = function (id, element, _transient) { 5042 if (!managedElements[id]) { 5043 managedElements[id] = { 5044 el: element, 5045 endpoints: [], 5046 connections: [] 5047 }; 5048 5049 managedElements[id].info = _updateOffset({ elId: id, timestamp: _suspendedAt }); 5050 if (!_transient) { 5051 _currentInstance.fire("manageElement", { id:id, info:managedElements[id].info, el:element }); 5052 } 5053 } 5054 5055 return managedElements[id]; 5056 }; 5057 5058 var _unmanage = function(id) { 5059 if (managedElements[id]) { 5060 delete managedElements[id]; 5061 _currentInstance.fire("unmanageElement", id); 5062 } 5063 }; 5064 5065 /** 5066 * updates the offset and size for a given element, and stores the 5067 * values. if 'offset' is not null we use that (it would have been 5068 * passed in from a drag call) because it's faster; but if it is null, 5069 * or if 'recalc' is true in order to force a recalculation, we get the current values. 5070 */ 5071 var _updateOffset = this.updateOffset = function (params) { 5072 5073 var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId, s; 5074 if (_suspendDrawing && !timestamp) { 5075 timestamp = _suspendedAt; 5076 } 5077 if (!recalc) { 5078 if (timestamp && timestamp === offsetTimestamps[elId]) { 5079 return {o: params.offset || offsets[elId], s: sizes[elId]}; 5080 } 5081 } 5082 if (recalc || (!offset && offsets[elId] == null)) { // if forced repaint or no offset available, we recalculate. 5083 5084 // get the current size and offset, and store them 5085 s = managedElements[elId] ? managedElements[elId].el : null; 5086 if (s != null) { 5087 sizes[elId] = _currentInstance.getSize(s); 5088 offsets[elId] = _currentInstance.getOffset(s); 5089 offsetTimestamps[elId] = timestamp; 5090 } 5091 } else { 5092 offsets[elId] = offset || offsets[elId]; 5093 if (sizes[elId] == null) { 5094 s = managedElements[elId].el; 5095 if (s != null) { 5096 sizes[elId] = _currentInstance.getSize(s); 5097 } 5098 } 5099 offsetTimestamps[elId] = timestamp; 5100 } 5101 5102 if (offsets[elId] && !offsets[elId].right) { 5103 offsets[elId].right = offsets[elId].left + sizes[elId][0]; 5104 offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; 5105 offsets[elId].width = sizes[elId][0]; 5106 offsets[elId].height = sizes[elId][1]; 5107 offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); 5108 offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); 5109 } 5110 5111 return {o: offsets[elId], s: sizes[elId]}; 5112 }; 5113 5114 /** 5115 * callback from the current library to tell us to prepare ourselves (attach 5116 * mouse listeners etc; can't do that until the library has provided a bind method) 5117 */ 5118 this.init = function () { 5119 rendererTypes = root.jsPlumb.getRenderModes(); 5120 5121 var _oneType = function (renderer, name, fn) { 5122 root.jsPlumb.Connectors[renderer][name] = function () { 5123 fn.apply(this, arguments); 5124 root.jsPlumb.ConnectorRenderers[renderer].apply(this, arguments); 5125 }; 5126 _ju.extend(root.jsPlumb.Connectors[renderer][name], [ fn, root.jsPlumb.ConnectorRenderers[renderer]]); 5127 }; 5128 5129 if (!root.jsPlumb.connectorsInitialized) { 5130 for (var i = 0; i < connectorTypes.length; i++) { 5131 for (var j = 0; j < rendererTypes.length; j++) { 5132 _oneType(rendererTypes[j], connectorTypes[i][1], connectorTypes[i][0]); 5133 } 5134 5135 } 5136 root.jsPlumb.connectorsInitialized = true; 5137 } 5138 5139 if (!initialized) { 5140 _getContainerFromDefaults(); 5141 _currentInstance.anchorManager = new root.jsPlumb.AnchorManager({jsPlumbInstance: _currentInstance}); 5142 initialized = true; 5143 _currentInstance.fire("ready", _currentInstance); 5144 } 5145 }.bind(this); 5146 5147 this.log = log; 5148 this.jsPlumbUIComponent = jsPlumbUIComponent; 5149 5150 /* 5151 * Creates an anchor with the given params. 5152 * 5153 * 5154 * Returns: The newly created Anchor. 5155 * Throws: an error if a named anchor was not found. 5156 */ 5157 this.makeAnchor = function () { 5158 var pp, _a = function (t, p) { 5159 if (root.jsPlumb.Anchors[t]) { 5160 return new root.jsPlumb.Anchors[t](p); 5161 } 5162 if (!_currentInstance.Defaults.DoNotThrowErrors) { 5163 throw { msg: "jsPlumb: unknown anchor type '" + t + "'" }; 5164 } 5165 }; 5166 if (arguments.length === 0) { 5167 return null; 5168 } 5169 var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; 5170 // if it appears to be an anchor already... 5171 if (specimen.compute && specimen.getOrientation) { 5172 return specimen; 5173 } //TODO hazy here about whether it should be added or is already added somehow. 5174 // is it the name of an anchor type? 5175 else if (typeof specimen === "string") { 5176 newAnchor = _a(arguments[0], {elementId: elementId, jsPlumbInstance: _currentInstance}); 5177 } 5178 // is it an array? it will be one of: 5179 // an array of [spec, params] - this defines a single anchor, which may be dynamic, but has parameters. 5180 // an array of arrays - this defines some dynamic anchors 5181 // an array of numbers - this defines a single anchor. 5182 else if (_ju.isArray(specimen)) { 5183 if (_ju.isArray(specimen[0]) || _ju.isString(specimen[0])) { 5184 // if [spec, params] format 5185 if (specimen.length === 2 && _ju.isObject(specimen[1])) { 5186 // if first arg is a string, its a named anchor with params 5187 if (_ju.isString(specimen[0])) { 5188 pp = root.jsPlumb.extend({elementId: elementId, jsPlumbInstance: _currentInstance}, specimen[1]); 5189 newAnchor = _a(specimen[0], pp); 5190 } 5191 // otherwise first arg is array, second is params. we treat as a dynamic anchor, which is fine 5192 // even if the first arg has only one entry. you could argue all anchors should be implicitly dynamic in fact. 5193 else { 5194 pp = root.jsPlumb.extend({elementId: elementId, jsPlumbInstance: _currentInstance, anchors: specimen[0]}, specimen[1]); 5195 newAnchor = new root.jsPlumb.DynamicAnchor(pp); 5196 } 5197 } 5198 else { 5199 newAnchor = new jsPlumb.DynamicAnchor({anchors: specimen, selector: null, elementId: elementId, jsPlumbInstance: _currentInstance}); 5200 } 5201 5202 } 5203 else { 5204 var anchorParams = { 5205 x: specimen[0], y: specimen[1], 5206 orientation: (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0, 0], 5207 offsets: (specimen.length >= 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], 5208 elementId: elementId, 5209 jsPlumbInstance: _currentInstance, 5210 cssClass: specimen.length === 7 ? specimen[6] : null 5211 }; 5212 newAnchor = new root.jsPlumb.Anchor(anchorParams); 5213 newAnchor.clone = function () { 5214 return new root.jsPlumb.Anchor(anchorParams); 5215 }; 5216 } 5217 } 5218 5219 if (!newAnchor.id) { 5220 newAnchor.id = "anchor_" + _idstamp(); 5221 } 5222 return newAnchor; 5223 }; 5224 5225 /** 5226 * makes a list of anchors from the given list of types or coords, eg 5227 * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] 5228 */ 5229 this.makeAnchors = function (types, elementId, jsPlumbInstance) { 5230 var r = []; 5231 for (var i = 0, ii = types.length; i < ii; i++) { 5232 if (typeof types[i] === "string") { 5233 r.push(root.jsPlumb.Anchors[types[i]]({elementId: elementId, jsPlumbInstance: jsPlumbInstance})); 5234 } 5235 else if (_ju.isArray(types[i])) { 5236 r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); 5237 } 5238 } 5239 return r; 5240 }; 5241 5242 /** 5243 * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor 5244 * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will 5245 * not need to provide this - i think). 5246 */ 5247 this.makeDynamicAnchor = function (anchors, anchorSelector) { 5248 return new root.jsPlumb.DynamicAnchor({anchors: anchors, selector: anchorSelector, elementId: null, jsPlumbInstance: _currentInstance}); 5249 }; 5250 5251 // --------------------- makeSource/makeTarget ---------------------------------------------- 5252 5253 this.targetEndpointDefinitions = {}; 5254 this.sourceEndpointDefinitions = {}; 5255 5256 var selectorFilter = function (evt, _el, selector, _instance, negate) { 5257 var t = evt.target || evt.srcElement, ok = false, 5258 sel = _instance.getSelector(_el, selector); 5259 for (var j = 0; j < sel.length; j++) { 5260 if (sel[j] === t) { 5261 ok = true; 5262 break; 5263 } 5264 } 5265 return negate ? !ok : ok; 5266 }; 5267 5268 var _makeElementDropHandler = function (elInfo, p, dropOptions, isSource, isTarget) { 5269 var proxyComponent = new jsPlumbUIComponent(p); 5270 var _drop = p._jsPlumb.EndpointDropHandler({ 5271 jsPlumb: _currentInstance, 5272 enabled: function () { 5273 return elInfo.def.enabled; 5274 }, 5275 isFull: function () { 5276 var targetCount = _currentInstance.select({target: elInfo.id}).length; 5277 return elInfo.def.maxConnections > 0 && targetCount >= elInfo.def.maxConnections; 5278 }, 5279 element: elInfo.el, 5280 elementId: elInfo.id, 5281 isSource: isSource, 5282 isTarget: isTarget, 5283 addClass: function (clazz) { 5284 _currentInstance.addClass(elInfo.el, clazz); 5285 }, 5286 removeClass: function (clazz) { 5287 _currentInstance.removeClass(elInfo.el, clazz); 5288 }, 5289 onDrop: function (jpc) { 5290 var source = jpc.endpoints[0]; 5291 source.anchor.locked = false; 5292 }, 5293 isDropAllowed: function () { 5294 return proxyComponent.isDropAllowed.apply(proxyComponent, arguments); 5295 }, 5296 isRedrop:function(jpc) { 5297 return (jpc.suspendedElement != null && jpc.suspendedEndpoint != null && jpc.suspendedEndpoint.element === elInfo.el); 5298 }, 5299 getEndpoint: function (jpc) { 5300 5301 // make a new Endpoint for the target, or get it from the cache if uniqueEndpoint 5302 // is set. if its a redrop the new endpoint will be immediately cleaned up. 5303 5304 var newEndpoint = elInfo.def.endpoint; 5305 5306 // if no cached endpoint, or there was one but it has been cleaned up 5307 // (ie. detached), create a new one 5308 if (newEndpoint == null || newEndpoint._jsPlumb == null) { 5309 var eps = _currentInstance.deriveEndpointAndAnchorSpec(jpc.getType().join(" "), true); 5310 var pp = eps.endpoints ? root.jsPlumb.extend(p, { 5311 endpoint:elInfo.def.def.endpoint || eps.endpoints[1] 5312 }) :p; 5313 if (eps.anchors) { 5314 pp = root.jsPlumb.extend(pp, { 5315 anchor:elInfo.def.def.anchor || eps.anchors[1] 5316 }); 5317 } 5318 newEndpoint = _currentInstance.addEndpoint(elInfo.el, pp); 5319 newEndpoint._mtNew = true; 5320 } 5321 5322 if (p.uniqueEndpoint) { 5323 elInfo.def.endpoint = newEndpoint; 5324 } 5325 5326 newEndpoint.setDeleteOnEmpty(true); 5327 5328 // if connection is detachable, init the new endpoint to be draggable, to support that happening. 5329 if (jpc.isDetachable()) { 5330 newEndpoint.initDraggable(); 5331 } 5332 5333 // if the anchor has a 'positionFinder' set, then delegate to that function to find 5334 // out where to locate the anchor. 5335 if (newEndpoint.anchor.positionFinder != null) { 5336 var dropPosition = _currentInstance.getUIPosition(arguments, _currentInstance.getZoom()), 5337 elPosition = _currentInstance.getOffset(elInfo.el), 5338 elSize = _currentInstance.getSize(elInfo.el), 5339 ap = dropPosition == null ? [0,0] : newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams); 5340 5341 newEndpoint.anchor.x = ap[0]; 5342 newEndpoint.anchor.y = ap[1]; 5343 // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to 5344 // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation 5345 // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be 5346 // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which 5347 // the target is furthest away from the source. 5348 } 5349 5350 return newEndpoint; 5351 }, 5352 maybeCleanup: function (ep) { 5353 if (ep._mtNew && ep.connections.length === 0) { 5354 _currentInstance.deleteObject({endpoint: ep}); 5355 } 5356 else { 5357 delete ep._mtNew; 5358 } 5359 } 5360 }); 5361 5362 // wrap drop events as needed and initialise droppable 5363 var dropEvent = root.jsPlumb.dragEvents.drop; 5364 dropOptions.scope = dropOptions.scope || (p.scope || _currentInstance.Defaults.Scope); 5365 dropOptions[dropEvent] = _ju.wrap(dropOptions[dropEvent], _drop, true); 5366 5367 // if target, return true from the over event. this will cause katavorio to stop setting drops to hover 5368 // if multipleDrop is set to false. 5369 if (isTarget) { 5370 dropOptions[root.jsPlumb.dragEvents.over] = function () { return true; }; 5371 } 5372 5373 // vanilla jsplumb only 5374 if (p.allowLoopback === false) { 5375 dropOptions.canDrop = function (_drag) { 5376 var de = _drag.getDragElement()._jsPlumbRelatedElement; 5377 return de !== elInfo.el; 5378 }; 5379 } 5380 _currentInstance.initDroppable(elInfo.el, dropOptions, "internal"); 5381 5382 return _drop; 5383 5384 }; 5385 5386 // see API docs 5387 this.makeTarget = function (el, params, referenceParams) { 5388 5389 // put jsplumb ref into params without altering the params passed in 5390 var p = root.jsPlumb.extend({_jsPlumb: this}, referenceParams); 5391 root.jsPlumb.extend(p, params); 5392 5393 var maxConnections = p.maxConnections || -1, 5394 5395 _doOne = function (el) { 5396 5397 // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, 5398 // and use the endpoint definition if found. 5399 // decode the info for this element (id and element) 5400 var elInfo = _info(el), 5401 elid = elInfo.id, 5402 dropOptions = root.jsPlumb.extend({}, p.dropOptions || {}), 5403 type = p.connectionType || "default"; 5404 5405 this.targetEndpointDefinitions[elid] = this.targetEndpointDefinitions[elid] || {}; 5406 5407 _ensureContainer(elid); 5408 5409 // if this is a group and the user has not mandated a rank, set to -1 so that Nodes takes 5410 // precedence. 5411 if (elInfo.el._isJsPlumbGroup && dropOptions.rank == null) { 5412 dropOptions.rank = -1; 5413 } 5414 5415 // store the definition 5416 var _def = { 5417 def: root.jsPlumb.extend({}, p), 5418 uniqueEndpoint: p.uniqueEndpoint, 5419 maxConnections: maxConnections, 5420 enabled: true 5421 }; 5422 elInfo.def = _def; 5423 this.targetEndpointDefinitions[elid][type] = _def; 5424 _makeElementDropHandler(elInfo, p, dropOptions, p.isSource === true, true); 5425 // stash the definition on the drop 5426 elInfo.el._katavorioDrop[elInfo.el._katavorioDrop.length - 1].targetDef = _def; 5427 5428 }.bind(this); 5429 5430 // make an array if only given one element 5431 var inputs = el.length && el.constructor !== String ? el : [ el ]; 5432 5433 // register each one in the list. 5434 for (var i = 0, ii = inputs.length; i < ii; i++) { 5435 _doOne(inputs[i]); 5436 } 5437 5438 return this; 5439 }; 5440 5441 // see api docs 5442 this.unmakeTarget = function (el, doNotClearArrays) { 5443 var info = _info(el); 5444 _currentInstance.destroyDroppable(info.el, "internal"); 5445 if (!doNotClearArrays) { 5446 delete this.targetEndpointDefinitions[info.id]; 5447 } 5448 5449 return this; 5450 }; 5451 5452 // see api docs 5453 this.makeSource = function (el, params, referenceParams) { 5454 var p = root.jsPlumb.extend({_jsPlumb: this}, referenceParams); 5455 root.jsPlumb.extend(p, params); 5456 var type = p.connectionType || "default"; 5457 var aae = _currentInstance.deriveEndpointAndAnchorSpec(type); 5458 p.endpoint = p.endpoint || aae.endpoints[0]; 5459 p.anchor = p.anchor || aae.anchors[0]; 5460 var maxConnections = p.maxConnections || -1, 5461 onMaxConnections = p.onMaxConnections, 5462 _doOne = function (elInfo) { 5463 // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, 5464 // and use the endpoint definition if found. 5465 var elid = elInfo.id, 5466 _del = this.getElement(elInfo.el); 5467 5468 this.sourceEndpointDefinitions[elid] = this.sourceEndpointDefinitions[elid] || {}; 5469 _ensureContainer(elid); 5470 5471 var _def = { 5472 def:root.jsPlumb.extend({}, p), 5473 uniqueEndpoint: p.uniqueEndpoint, 5474 maxConnections: maxConnections, 5475 enabled: true 5476 }; 5477 5478 5479 this.sourceEndpointDefinitions[elid][type] = _def; 5480 elInfo.def = _def; 5481 5482 var stopEvent = root.jsPlumb.dragEvents.stop, 5483 dragEvent = root.jsPlumb.dragEvents.drag, 5484 dragOptions = root.jsPlumb.extend({ }, p.dragOptions || {}), 5485 existingDrag = dragOptions.drag, 5486 existingStop = dragOptions.stop, 5487 ep = null, 5488 endpointAddedButNoDragYet = false; 5489 5490 // set scope if its not set in dragOptions but was passed in in params 5491 dragOptions.scope = dragOptions.scope || p.scope; 5492 5493 dragOptions[dragEvent] = _ju.wrap(dragOptions[dragEvent], function () { 5494 if (existingDrag) { 5495 existingDrag.apply(this, arguments); 5496 } 5497 endpointAddedButNoDragYet = false; 5498 }); 5499 5500 dragOptions[stopEvent] = _ju.wrap(dragOptions[stopEvent], function () { 5501 5502 if (existingStop) { 5503 existingStop.apply(this, arguments); 5504 } 5505 this.currentlyDragging = false; 5506 if (ep._jsPlumb != null) { // if not cleaned up... 5507 5508 // reset the anchor to the anchor that was initially provided. the one we were using to drag 5509 // the connection was just a placeholder that was located at the place the user pressed the 5510 // mouse button to initiate the drag. 5511 var anchorDef = p.anchor || this.Defaults.Anchor, 5512 oldAnchor = ep.anchor, 5513 oldConnection = ep.connections[0]; 5514 5515 var newAnchor = this.makeAnchor(anchorDef, elid, this), 5516 _el = ep.element; 5517 5518 // if the anchor has a 'positionFinder' set, then delegate to that function to find 5519 // out where to locate the anchor. issue 117. 5520 if (newAnchor.positionFinder != null) { 5521 var elPosition = _currentInstance.getOffset(_el), 5522 elSize = this.getSize(_el), 5523 dropPosition = { left: elPosition.left + (oldAnchor.x * elSize[0]), top: elPosition.top + (oldAnchor.y * elSize[1]) }, 5524 ap = newAnchor.positionFinder(dropPosition, elPosition, elSize, newAnchor.constructorParams); 5525 5526 newAnchor.x = ap[0]; 5527 newAnchor.y = ap[1]; 5528 } 5529 5530 ep.setAnchor(newAnchor, true); 5531 ep.repaint(); 5532 this.repaint(ep.elementId); 5533 if (oldConnection != null) { 5534 this.repaint(oldConnection.targetId); 5535 } 5536 } 5537 }.bind(this)); 5538 5539 // when the user presses the mouse, add an Endpoint, if we are enabled. 5540 var mouseDownListener = function (e) { 5541 // on right mouse button, abort. 5542 if (e.which === 3 || e.button === 2) { 5543 return; 5544 } 5545 5546 // TODO store def on element. 5547 var def = this.sourceEndpointDefinitions[elid][type]; 5548 5549 // if disabled, return. 5550 if (!def.enabled) { 5551 return; 5552 } 5553 5554 elid = this.getId(this.getElement(elInfo.el)); // elid might have changed since this method was called to configure the element. 5555 5556 // if a filter was given, run it, and return if it says no. 5557 if (p.filter) { 5558 var r = _ju.isString(p.filter) ? selectorFilter(e, elInfo.el, p.filter, this, p.filterExclude) : p.filter(e, elInfo.el); 5559 if (r === false) { 5560 return; 5561 } 5562 } 5563 5564 // if maxConnections reached 5565 var sourceCount = this.select({source: elid}).length; 5566 if (def.maxConnections >= 0 && (sourceCount >= def.maxConnections)) { 5567 if (onMaxConnections) { 5568 onMaxConnections({ 5569 element: elInfo.el, 5570 maxConnections: maxConnections 5571 }, e); 5572 } 5573 return false; 5574 } 5575 5576 // find the position on the element at which the mouse was pressed; this is where the endpoint 5577 // will be located. 5578 var elxy = root.jsPlumb.getPositionOnElement(e, _del, _zoom); 5579 5580 // we need to override the anchor in here, and force 'isSource', but we don't want to mess with 5581 // the params passed in, because after a connection is established we're going to reset the endpoint 5582 // to have the anchor we were given. 5583 var tempEndpointParams = {}; 5584 root.jsPlumb.extend(tempEndpointParams, p); 5585 tempEndpointParams.isTemporarySource = true; 5586 tempEndpointParams.anchor = [ elxy[0], elxy[1] , 0, 0]; 5587 tempEndpointParams.dragOptions = dragOptions; 5588 5589 if (def.def.scope) { 5590 tempEndpointParams.scope = def.def.scope; 5591 } 5592 5593 ep = this.addEndpoint(elid, tempEndpointParams); 5594 endpointAddedButNoDragYet = true; 5595 ep.setDeleteOnEmpty(true); 5596 5597 // if unique endpoint and it's already been created, push it onto the endpoint we create. at the end 5598 // of a successful connection we'll switch to that endpoint. 5599 // TODO this is the same code as the programmatic endpoints create on line 1050 ish 5600 if (def.uniqueEndpoint) { 5601 if (!def.endpoint) { 5602 def.endpoint = ep; 5603 ep.setDeleteOnEmpty(false); 5604 } 5605 else { 5606 ep.finalEndpoint = def.endpoint; 5607 } 5608 } 5609 5610 var _delTempEndpoint = function () { 5611 // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools 5612 // it is fired even if dragging has occurred, in which case we would blow away a perfectly 5613 // legitimate endpoint, were it not for this check. the flag is set after adding an 5614 // endpoint and cleared in a drag listener we set in the dragOptions above. 5615 _currentInstance.off(ep.canvas, "mouseup", _delTempEndpoint); 5616 _currentInstance.off(elInfo.el, "mouseup", _delTempEndpoint); 5617 if (endpointAddedButNoDragYet) { 5618 endpointAddedButNoDragYet = false; 5619 _currentInstance.deleteEndpoint(ep); 5620 } 5621 }; 5622 5623 _currentInstance.on(ep.canvas, "mouseup", _delTempEndpoint); 5624 _currentInstance.on(elInfo.el, "mouseup", _delTempEndpoint); 5625 5626 // optionally check for attributes to extract from the source element 5627 var payload = {}; 5628 if (def.def.extract) { 5629 for (var att in def.def.extract) { 5630 var v = (e.srcElement || e.target).getAttribute(att); 5631 if (v) { 5632 payload[def.def.extract[att]] = v; 5633 } 5634 } 5635 } 5636 5637 // and then trigger its mousedown event, which will kick off a drag, which will start dragging 5638 // a new connection from this endpoint. 5639 _currentInstance.trigger(ep.canvas, "mousedown", e, payload); 5640 5641 _ju.consume(e); 5642 5643 }.bind(this); 5644 5645 this.on(elInfo.el, "mousedown", mouseDownListener); 5646 _def.trigger = mouseDownListener; 5647 5648 // if a filter was provided, set it as a dragFilter on the element, 5649 // to prevent the element drag function from kicking in when we want to 5650 // drag a new connection 5651 if (p.filter && (_ju.isString(p.filter) || _ju.isFunction(p.filter))) { 5652 _currentInstance.setDragFilter(elInfo.el, p.filter); 5653 } 5654 5655 var dropOptions = root.jsPlumb.extend({}, p.dropOptions || {}); 5656 5657 _makeElementDropHandler(elInfo, p, dropOptions, true, p.isTarget === true); 5658 5659 }.bind(this); 5660 5661 var inputs = el.length && el.constructor !== String ? el : [ el ]; 5662 for (var i = 0, ii = inputs.length; i < ii; i++) { 5663 _doOne(_info(inputs[i])); 5664 } 5665 5666 return this; 5667 }; 5668 5669 // see api docs 5670 this.unmakeSource = function (el, connectionType, doNotClearArrays) { 5671 var info = _info(el); 5672 _currentInstance.destroyDroppable(info.el, "internal"); 5673 var eldefs = this.sourceEndpointDefinitions[info.id]; 5674 if (eldefs) { 5675 for (var def in eldefs) { 5676 if (connectionType == null || connectionType === def) { 5677 var mouseDownListener = eldefs[def].trigger; 5678 if (mouseDownListener) { 5679 _currentInstance.off(info.el, "mousedown", mouseDownListener); 5680 } 5681 if (!doNotClearArrays) { 5682 delete this.sourceEndpointDefinitions[info.id][def]; 5683 } 5684 } 5685 } 5686 } 5687 5688 return this; 5689 }; 5690 5691 // see api docs 5692 this.unmakeEverySource = function () { 5693 for (var i in this.sourceEndpointDefinitions) { 5694 _currentInstance.unmakeSource(i, null, true); 5695 } 5696 5697 this.sourceEndpointDefinitions = {}; 5698 return this; 5699 }; 5700 5701 var _getScope = function (el, types, connectionType) { 5702 types = _ju.isArray(types) ? types : [ types ]; 5703 var id = _getId(el); 5704 connectionType = connectionType || "default"; 5705 for (var i = 0; i < types.length; i++) { 5706 var eldefs = this[types[i]][id]; 5707 if (eldefs && eldefs[connectionType]) { 5708 return eldefs[connectionType].def.scope || this.Defaults.Scope; 5709 } 5710 } 5711 }.bind(this); 5712 5713 var _setScope = function (el, scope, types, connectionType) { 5714 types = _ju.isArray(types) ? types : [ types ]; 5715 var id = _getId(el); 5716 connectionType = connectionType || "default"; 5717 for (var i = 0; i < types.length; i++) { 5718 var eldefs = this[types[i]][id]; 5719 if (eldefs && eldefs[connectionType]) { 5720 eldefs[connectionType].def.scope = scope; 5721 } 5722 } 5723 5724 }.bind(this); 5725 5726 this.getScope = function (el, scope) { 5727 return _getScope(el, [ "sourceEndpointDefinitions", "targetEndpointDefinitions" ]); 5728 }; 5729 this.getSourceScope = function (el) { 5730 return _getScope(el, "sourceEndpointDefinitions"); 5731 }; 5732 this.getTargetScope = function (el) { 5733 return _getScope(el, "targetEndpointDefinitions"); 5734 }; 5735 this.setScope = function (el, scope, connectionType) { 5736 this.setSourceScope(el, scope, connectionType); 5737 this.setTargetScope(el, scope, connectionType); 5738 }; 5739 this.setSourceScope = function (el, scope, connectionType) { 5740 _setScope(el, scope, "sourceEndpointDefinitions", connectionType); 5741 // we get the source scope during the mousedown event, but we also want to set this. 5742 this.setDragScope(el, scope); 5743 }; 5744 this.setTargetScope = function (el, scope, connectionType) { 5745 _setScope(el, scope, "targetEndpointDefinitions", connectionType); 5746 this.setDropScope(el, scope); 5747 }; 5748 5749 // see api docs 5750 this.unmakeEveryTarget = function () { 5751 for (var i in this.targetEndpointDefinitions) { 5752 _currentInstance.unmakeTarget(i, true); 5753 } 5754 5755 this.targetEndpointDefinitions = {}; 5756 return this; 5757 }; 5758 5759 // does the work of setting a source enabled or disabled. 5760 var _setEnabled = function (type, el, state, toggle, connectionType) { 5761 var a = type === "source" ? this.sourceEndpointDefinitions : this.targetEndpointDefinitions, 5762 originalState, info, newState; 5763 5764 connectionType = connectionType || "default"; 5765 5766 // a selector or an array 5767 if (el.length && !_ju.isString(el)) { 5768 originalState = []; 5769 for (var i = 0, ii = el.length; i < ii; i++) { 5770 info = _info(el[i]); 5771 if (a[info.id] && a[info.id][connectionType]) { 5772 originalState[i] = a[info.id][connectionType].enabled; 5773 newState = toggle ? !originalState[i] : state; 5774 a[info.id][connectionType].enabled = newState; 5775 _currentInstance[newState ? "removeClass" : "addClass"](info.el, "jtk-" + type + "-disabled"); 5776 } 5777 } 5778 } 5779 // otherwise a DOM element or a String ID. 5780 else { 5781 info = _info(el); 5782 var id = info.id; 5783 if (a[id] && a[id][connectionType]) { 5784 originalState = a[id][connectionType].enabled; 5785 newState = toggle ? !originalState : state; 5786 a[id][connectionType].enabled = newState; 5787 _currentInstance[newState ? "removeClass" : "addClass"](info.el, "jtk-" + type + "-disabled"); 5788 } 5789 } 5790 return originalState; 5791 }.bind(this); 5792 5793 var _first = function (el, fn) { 5794 if (_ju.isString(el) || !el.length) { 5795 return fn.apply(this, [ el ]); 5796 } 5797 else if (el.length) { 5798 return fn.apply(this, [ el[0] ]); 5799 } 5800 5801 }.bind(this); 5802 5803 this.toggleSourceEnabled = function (el, connectionType) { 5804 _setEnabled("source", el, null, true, connectionType); 5805 return this.isSourceEnabled(el, connectionType); 5806 }; 5807 5808 this.setSourceEnabled = function (el, state, connectionType) { 5809 return _setEnabled("source", el, state, null, connectionType); 5810 }; 5811 this.isSource = function (el, connectionType) { 5812 connectionType = connectionType || "default"; 5813 return _first(el, function (_el) { 5814 var eldefs = this.sourceEndpointDefinitions[_info(_el).id]; 5815 return eldefs != null && eldefs[connectionType] != null; 5816 }.bind(this)); 5817 }; 5818 this.isSourceEnabled = function (el, connectionType) { 5819 connectionType = connectionType || "default"; 5820 return _first(el, function (_el) { 5821 var sep = this.sourceEndpointDefinitions[_info(_el).id]; 5822 return sep && sep[connectionType] && sep[connectionType].enabled === true; 5823 }.bind(this)); 5824 }; 5825 5826 this.toggleTargetEnabled = function (el, connectionType) { 5827 _setEnabled("target", el, null, true, connectionType); 5828 return this.isTargetEnabled(el, connectionType); 5829 }; 5830 5831 this.isTarget = function (el, connectionType) { 5832 connectionType = connectionType || "default"; 5833 return _first(el, function (_el) { 5834 var eldefs = this.targetEndpointDefinitions[_info(_el).id]; 5835 return eldefs != null && eldefs[connectionType] != null; 5836 }.bind(this)); 5837 }; 5838 this.isTargetEnabled = function (el, connectionType) { 5839 connectionType = connectionType || "default"; 5840 return _first(el, function (_el) { 5841 var tep = this.targetEndpointDefinitions[_info(_el).id]; 5842 return tep && tep[connectionType] && tep[connectionType].enabled === true; 5843 }.bind(this)); 5844 }; 5845 this.setTargetEnabled = function (el, state, connectionType) { 5846 return _setEnabled("target", el, state, null, connectionType); 5847 }; 5848 5849 // --------------------- end makeSource/makeTarget ---------------------------------------------- 5850 5851 this.ready = function (fn) { 5852 _currentInstance.bind("ready", fn); 5853 }; 5854 5855 var _elEach = function(el, fn) { 5856 // support both lists... 5857 if (typeof el === 'object' && el.length) { 5858 for (var i = 0, ii = el.length; i < ii; i++) { 5859 fn(el[i]); 5860 } 5861 } 5862 else {// ...and single strings or elements. 5863 fn(el); 5864 } 5865 5866 return _currentInstance; 5867 }; 5868 5869 // repaint some element's endpoints and connections 5870 this.repaint = function (el, ui, timestamp) { 5871 return _elEach(el, function(_el) { 5872 _draw(_el, ui, timestamp); 5873 }); 5874 }; 5875 5876 this.revalidate = function (el, timestamp, isIdAlready) { 5877 return _elEach(el, function(_el) { 5878 var elId = isIdAlready ? _el : _currentInstance.getId(_el); 5879 _currentInstance.updateOffset({ elId: elId, recalc: true, timestamp:timestamp }); 5880 var dm = _currentInstance.getDragManager(); 5881 if (dm) { 5882 dm.updateOffsets(elId); 5883 } 5884 _currentInstance.repaint(_el); 5885 }); 5886 }; 5887 5888 // repaint every endpoint and connection. 5889 this.repaintEverything = function () { 5890 // TODO this timestamp causes continuous anchors to not repaint properly. 5891 // fix this. do not just take out the timestamp. it runs a lot faster with 5892 // the timestamp included. 5893 var timestamp = _timestamp(), elId; 5894 5895 for (elId in endpointsByElement) { 5896 _currentInstance.updateOffset({ elId: elId, recalc: true, timestamp: timestamp }); 5897 } 5898 5899 for (elId in endpointsByElement) { 5900 _draw(elId, null, timestamp); 5901 } 5902 5903 return this; 5904 }; 5905 5906 this.removeAllEndpoints = function (el, recurse, affectedElements) { 5907 affectedElements = affectedElements || []; 5908 var _one = function (_el) { 5909 var info = _info(_el), 5910 ebe = endpointsByElement[info.id], 5911 i, ii; 5912 5913 if (ebe) { 5914 affectedElements.push(info); 5915 for (i = 0, ii = ebe.length; i < ii; i++) { 5916 _currentInstance.deleteEndpoint(ebe[i], false); 5917 } 5918 } 5919 delete endpointsByElement[info.id]; 5920 5921 if (recurse) { 5922 if (info.el && info.el.nodeType !== 3 && info.el.nodeType !== 8) { 5923 for (i = 0, ii = info.el.childNodes.length; i < ii; i++) { 5924 _one(info.el.childNodes[i]); 5925 } 5926 } 5927 } 5928 5929 }; 5930 _one(el); 5931 return this; 5932 }; 5933 5934 var _doRemove = function(info, affectedElements) { 5935 _currentInstance.removeAllEndpoints(info.id, true, affectedElements); 5936 var dm = _currentInstance.getDragManager(); 5937 var _one = function(_info) { 5938 5939 if (dm) { 5940 dm.elementRemoved(_info.id); 5941 } 5942 _currentInstance.anchorManager.clearFor(_info.id); 5943 _currentInstance.anchorManager.removeFloatingConnection(_info.id); 5944 5945 if (_currentInstance.isSource(_info.el)) { 5946 _currentInstance.unmakeSource(_info.el); 5947 } 5948 if (_currentInstance.isTarget(_info.el)) { 5949 _currentInstance.unmakeTarget(_info.el); 5950 } 5951 _currentInstance.destroyDraggable(_info.el); 5952 _currentInstance.destroyDroppable(_info.el); 5953 5954 5955 delete _currentInstance.floatingConnections[_info.id]; 5956 delete managedElements[_info.id]; 5957 delete offsets[_info.id]; 5958 if (_info.el) { 5959 _currentInstance.removeElement(_info.el); 5960 _info.el._jsPlumb = null; 5961 } 5962 }; 5963 5964 // remove all affected child elements 5965 for (var ae = 1; ae < affectedElements.length; ae++) { 5966 _one(affectedElements[ae]); 5967 } 5968 // and always remove the requested one from the dom. 5969 _one(info); 5970 }; 5971 5972 /** 5973 * Remove the given element, including cleaning up all endpoints registered for it. 5974 * This is exposed in the public API but also used internally by jsPlumb when removing the 5975 * element associated with a connection drag. 5976 */ 5977 this.remove = function (el, doNotRepaint) { 5978 var info = _info(el), affectedElements = []; 5979 if (info.text) { 5980 info.el.parentNode.removeChild(info.el); 5981 } 5982 else if (info.id) { 5983 _currentInstance.batch(function () { 5984 _doRemove(info, affectedElements); 5985 }, doNotRepaint === false); 5986 } 5987 return _currentInstance; 5988 }; 5989 5990 this.empty = function (el, doNotRepaint) { 5991 var affectedElements = []; 5992 var _one = function(el, dontRemoveFocus) { 5993 var info = _info(el); 5994 if (info.text) { 5995 info.el.parentNode.removeChild(info.el); 5996 } 5997 else if (info.el) { 5998 while(info.el.childNodes.length > 0) { 5999 _one(info.el.childNodes[0]); 6000 } 6001 if (!dontRemoveFocus) { 6002 _doRemove(info, affectedElements); 6003 } 6004 } 6005 }; 6006 6007 _currentInstance.batch(function() { 6008 _one(el, true); 6009 }, doNotRepaint === false); 6010 6011 return _currentInstance; 6012 }; 6013 6014 this.reset = function () { 6015 _currentInstance.silently(function() { 6016 _hoverSuspended = false; 6017 _currentInstance.removeAllGroups(); 6018 _currentInstance.removeGroupManager(); 6019 _currentInstance.deleteEveryEndpoint(); 6020 _currentInstance.unbind(); 6021 this.targetEndpointDefinitions = {}; 6022 this.sourceEndpointDefinitions = {}; 6023 connections.length = 0; 6024 if (this.doReset) { 6025 this.doReset(); 6026 } 6027 }.bind(this)); 6028 }; 6029 6030 var _clearObject = function (obj) { 6031 if (obj.canvas && obj.canvas.parentNode) { 6032 obj.canvas.parentNode.removeChild(obj.canvas); 6033 } 6034 obj.cleanup(); 6035 obj.destroy(); 6036 }; 6037 6038 this.clear = function () { 6039 _currentInstance.select().each(_clearObject); 6040 _currentInstance.selectEndpoints().each(_clearObject); 6041 6042 endpointsByElement = {}; 6043 endpointsByUUID = {}; 6044 }; 6045 6046 this.setDefaultScope = function (scope) { 6047 DEFAULT_SCOPE = scope; 6048 return _currentInstance; 6049 }; 6050 6051 // sets whether or not some element should be currently draggable. 6052 this.setDraggable = _setDraggable; 6053 6054 this.deriveEndpointAndAnchorSpec = function(type, dontPrependDefault) { 6055 var bits = ((dontPrependDefault ? "" : "default ") + type).split(/[\s]/), eps = null, ep = null, a = null, as = null; 6056 for (var i = 0; i < bits.length; i++) { 6057 var _t = _currentInstance.getType(bits[i], "connection"); 6058 if (_t) { 6059 if (_t.endpoints) { 6060 eps = _t.endpoints; 6061 } 6062 if (_t.endpoint) { 6063 ep = _t.endpoint; 6064 } 6065 if (_t.anchors) { 6066 as = _t.anchors; 6067 } 6068 if (_t.anchor) { 6069 a = _t.anchor; 6070 } 6071 } 6072 } 6073 return { endpoints: eps ? eps : [ ep, ep ], anchors: as ? as : [a, a ]}; 6074 }; 6075 6076 // sets the id of some element, changing whatever we need to to keep track. 6077 this.setId = function (el, newId, doNotSetAttribute) { 6078 // 6079 var id; 6080 6081 if (_ju.isString(el)) { 6082 id = el; 6083 } 6084 else { 6085 el = this.getElement(el); 6086 id = this.getId(el); 6087 } 6088 6089 var sConns = this.getConnections({source: id, scope: '*'}, true), 6090 tConns = this.getConnections({target: id, scope: '*'}, true); 6091 6092 newId = "" + newId; 6093 6094 if (!doNotSetAttribute) { 6095 el = this.getElement(id); 6096 this.setAttribute(el, "id", newId); 6097 } 6098 else { 6099 el = this.getElement(newId); 6100 } 6101 6102 endpointsByElement[newId] = endpointsByElement[id] || []; 6103 for (var i = 0, ii = endpointsByElement[newId].length; i < ii; i++) { 6104 endpointsByElement[newId][i].setElementId(newId); 6105 endpointsByElement[newId][i].setReferenceElement(el); 6106 } 6107 delete endpointsByElement[id]; 6108 6109 this.sourceEndpointDefinitions[newId] = this.sourceEndpointDefinitions[id]; 6110 delete this.sourceEndpointDefinitions[id]; 6111 this.targetEndpointDefinitions[newId] = this.targetEndpointDefinitions[id]; 6112 delete this.targetEndpointDefinitions[id]; 6113 6114 this.anchorManager.changeId(id, newId); 6115 var dm = this.getDragManager(); 6116 if (dm) { 6117 dm.changeId(id, newId); 6118 } 6119 managedElements[newId] = managedElements[id]; 6120 delete managedElements[id]; 6121 6122 var _conns = function (list, epIdx, type) { 6123 for (var i = 0, ii = list.length; i < ii; i++) { 6124 list[i].endpoints[epIdx].setElementId(newId); 6125 list[i].endpoints[epIdx].setReferenceElement(el); 6126 list[i][type + "Id"] = newId; 6127 list[i][type] = el; 6128 } 6129 }; 6130 _conns(sConns, 0, "source"); 6131 _conns(tConns, 1, "target"); 6132 6133 this.repaint(newId); 6134 }; 6135 6136 this.setDebugLog = function (debugLog) { 6137 log = debugLog; 6138 }; 6139 6140 this.setSuspendDrawing = function (val, repaintAfterwards) { 6141 var curVal = _suspendDrawing; 6142 _suspendDrawing = val; 6143 if (val) { 6144 _suspendedAt = new Date().getTime(); 6145 } else { 6146 _suspendedAt = null; 6147 } 6148 if (repaintAfterwards) { 6149 this.repaintEverything(); 6150 } 6151 return curVal; 6152 }; 6153 6154 // returns whether or not drawing is currently suspended. 6155 this.isSuspendDrawing = function () { 6156 return _suspendDrawing; 6157 }; 6158 6159 // return timestamp for when drawing was suspended. 6160 this.getSuspendedAt = function () { 6161 return _suspendedAt; 6162 }; 6163 6164 this.batch = function (fn, doNotRepaintAfterwards) { 6165 var _wasSuspended = this.isSuspendDrawing(); 6166 if (!_wasSuspended) { 6167 this.setSuspendDrawing(true); 6168 } 6169 try { 6170 fn(); 6171 } 6172 catch (e) { 6173 _ju.log("Function run while suspended failed", e); 6174 } 6175 if (!_wasSuspended) { 6176 this.setSuspendDrawing(false, !doNotRepaintAfterwards); 6177 } 6178 }; 6179 6180 this.doWhileSuspended = this.batch; 6181 6182 this.getCachedData = _getCachedData; 6183 this.timestamp = _timestamp; 6184 this.show = function (el, changeEndpoints) { 6185 _setVisible(el, "block", changeEndpoints); 6186 return _currentInstance; 6187 }; 6188 6189 // TODO: update this method to return the current state. 6190 this.toggleVisible = _toggleVisible; 6191 this.toggleDraggable = _toggleDraggable; 6192 this.addListener = this.bind; 6193 }; 6194 6195 _ju.extend(root.jsPlumbInstance, _ju.EventGenerator, { 6196 setAttribute: function (el, a, v) { 6197 this.setAttribute(el, a, v); 6198 }, 6199 getAttribute: function (el, a) { 6200 return this.getAttribute(root.jsPlumb.getElement(el), a); 6201 }, 6202 convertToFullOverlaySpec: function(spec) { 6203 if (_ju.isString(spec)) { 6204 spec = [ spec, { } ]; 6205 } 6206 spec[1].id = spec[1].id || _ju.uuid(); 6207 return spec; 6208 }, 6209 registerConnectionType: function (id, type) { 6210 this._connectionTypes[id] = root.jsPlumb.extend({}, type); 6211 if (type.overlays) { 6212 var to = {}; 6213 for (var i = 0; i < type.overlays.length; i++) { 6214 // if a string, convert to object representation so that we can store the typeid on it. 6215 // also assign an id. 6216 var fo = this.convertToFullOverlaySpec(type.overlays[i]); 6217 to[fo[1].id] = fo; 6218 } 6219 this._connectionTypes[id].overlays = to; 6220 } 6221 }, 6222 registerConnectionTypes: function (types) { 6223 for (var i in types) { 6224 this.registerConnectionType(i, types[i]); 6225 } 6226 }, 6227 registerEndpointType: function (id, type) { 6228 this._endpointTypes[id] = root.jsPlumb.extend({}, type); 6229 if (type.overlays) { 6230 var to = {}; 6231 for (var i = 0; i < type.overlays.length; i++) { 6232 // if a string, convert to object representation so that we can store the typeid on it. 6233 // also assign an id. 6234 var fo = this.convertToFullOverlaySpec(type.overlays[i]); 6235 to[fo[1].id] = fo; 6236 } 6237 this._endpointTypes[id].overlays = to; 6238 } 6239 }, 6240 registerEndpointTypes: function (types) { 6241 for (var i in types) { 6242 this.registerEndpointType(i, types[i]); 6243 } 6244 }, 6245 getType: function (id, typeDescriptor) { 6246 return typeDescriptor === "connection" ? this._connectionTypes[id] : this._endpointTypes[id]; 6247 }, 6248 setIdChanged: function (oldId, newId) { 6249 this.setId(oldId, newId, true); 6250 }, 6251 // set parent: change the parent for some node and update all the registrations we need to. 6252 setParent: function (el, newParent) { 6253 var _dom = this.getElement(el), 6254 _id = this.getId(_dom), 6255 _pdom = this.getElement(newParent), 6256 _pid = this.getId(_pdom), 6257 dm = this.getDragManager(); 6258 6259 _dom.parentNode.removeChild(_dom); 6260 _pdom.appendChild(_dom); 6261 if (dm) { 6262 dm.setParent(_dom, _id, _pdom, _pid); 6263 } 6264 }, 6265 extend: function (o1, o2, names) { 6266 var i; 6267 if (names) { 6268 for (i = 0; i < names.length; i++) { 6269 o1[names[i]] = o2[names[i]]; 6270 } 6271 } 6272 else { 6273 for (i in o2) { 6274 o1[i] = o2[i]; 6275 } 6276 } 6277 6278 return o1; 6279 }, 6280 floatingConnections: {}, 6281 getFloatingAnchorIndex: function (jpc) { 6282 return jpc.endpoints[0].isFloating() ? 0 : jpc.endpoints[1].isFloating() ? 1 : -1; 6283 } 6284 }); 6285 6286 jsPlumbInstance.prototype.Defaults = { 6287 Anchor: "Bottom", 6288 Anchors: [ null, null ], 6289 ConnectionsDetachable: true, 6290 ConnectionOverlays: [ ], 6291 Connector: "Bezier", 6292 Container: null, 6293 DoNotThrowErrors: false, 6294 DragOptions: { }, 6295 DropOptions: { }, 6296 Endpoint: "Dot", 6297 EndpointOverlays: [ ], 6298 Endpoints: [ null, null ], 6299 EndpointStyle: { fill: "#456" }, 6300 EndpointStyles: [ null, null ], 6301 EndpointHoverStyle: null, 6302 EndpointHoverStyles: [ null, null ], 6303 HoverPaintStyle: null, 6304 LabelStyle: { color: "black" }, 6305 LogEnabled: false, 6306 Overlays: [ ], 6307 MaxConnections: 1, 6308 PaintStyle: { "stroke-width": 4, stroke: "#456" }, 6309 ReattachConnections: false, 6310 RenderMode: "svg", 6311 Scope: "jsPlumb_DefaultScope" 6312 }; 6313 6314 // --------------------- static instance + module registration ------------------------------------------- 6315 6316 // create static instance and assign to window if window exists. 6317 var jsPlumb = new jsPlumbInstance(); 6318 // register on 'root' (lets us run on server or browser) 6319 root.jsPlumb = jsPlumb; 6320 // add 'getInstance' method to static instance 6321 jsPlumb.getInstance = function (_defaults, overrideFns) { 6322 var j = new jsPlumbInstance(_defaults); 6323 if (overrideFns) { 6324 for (var ovf in overrideFns) { 6325 j[ovf] = overrideFns[ovf]; 6326 } 6327 } 6328 j.init(); 6329 return j; 6330 }; 6331 jsPlumb.each = function (spec, fn) { 6332 if (spec == null) { 6333 return; 6334 } 6335 if (typeof spec === "string") { 6336 fn(jsPlumb.getElement(spec)); 6337 } 6338 else if (spec.length != null) { 6339 for (var i = 0; i < spec.length; i++) { 6340 fn(jsPlumb.getElement(spec[i])); 6341 } 6342 } 6343 else { 6344 fn(spec); 6345 } // assume it's an element. 6346 }; 6347 6348 // CommonJS 6349 if (typeof exports !== 'undefined') { 6350 exports.jsPlumb = jsPlumb; 6351 } 6352 6353 // --------------------- end static instance + AMD registration ------------------------------------------- 6354 6355 }).call(typeof window !== 'undefined' ? window : this); 6356 6357 /* 6358 * jsPlumb Community Edition 6359 * 6360 * Provides a way to visually connect elements on an HTML page, using SVG. 6361 * 6362 * This file contains the base functionality for DOM type adapters. 6363 * 6364 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 6365 * 6366 * https://jsplumbtoolkit.com 6367 * https://github.com/jsplumb/jsplumb 6368 * 6369 * Dual licensed under the MIT and GPL2 licenses. 6370 */ 6371 ; 6372 (function () { 6373 6374 var root = this, _ju = root.jsPlumbUtil; 6375 6376 var _genLoc = function (prefix, e) { 6377 if (e == null) { 6378 return [ 0, 0 ]; 6379 } 6380 var ts = _touches(e), t = _getTouch(ts, 0); 6381 return [t[prefix + "X"], t[prefix + "Y"]]; 6382 }, 6383 _pageLocation = _genLoc.bind(this, "page"), 6384 _screenLocation = _genLoc.bind(this, "screen"), 6385 _clientLocation = _genLoc.bind(this, "client"), 6386 _getTouch = function (touches, idx) { 6387 return touches.item ? touches.item(idx) : touches[idx]; 6388 }, 6389 _touches = function (e) { 6390 return e.touches && e.touches.length > 0 ? e.touches : 6391 e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches : 6392 e.targetTouches && e.targetTouches.length > 0 ? e.targetTouches : 6393 [ e ]; 6394 }; 6395 6396 /** 6397 Manages dragging for some instance of jsPlumb. 6398 6399 TODO instead of this being accessed directly, it should subscribe to events on the jsPlumb instance: every method 6400 in here is called directly by jsPlumb. But what should happen is that we have unpublished events that this listens 6401 to. The only trick is getting one of these instantiated with every jsPlumb instance: it needs to have a hook somehow. 6402 Basically the general idea is to pull ALL the drag code out (prototype method registrations plus this) into a 6403 dedicated drag script), that does not necessarily need to be included. 6404 6405 6406 */ 6407 var DragManager = function (_currentInstance) { 6408 var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {}, 6409 // elementids mapped to the draggable to which they belong. 6410 _draggablesForElements = {}; 6411 6412 /** 6413 register some element as draggable. right now the drag init stuff is done elsewhere, and it is 6414 possible that will continue to be the case. 6415 */ 6416 this.register = function (el) { 6417 var id = _currentInstance.getId(el), 6418 parentOffset = _currentInstance.getOffset(el); 6419 6420 if (!_draggables[id]) { 6421 _draggables[id] = el; 6422 _dlist.push(el); 6423 _delements[id] = {}; 6424 } 6425 6426 // look for child elements that have endpoints and register them against this draggable. 6427 var _oneLevel = function (p) { 6428 if (p) { 6429 for (var i = 0; i < p.childNodes.length; i++) { 6430 if (p.childNodes[i].nodeType !== 3 && p.childNodes[i].nodeType !== 8) { 6431 var cEl = jsPlumb.getElement(p.childNodes[i]), 6432 cid = _currentInstance.getId(p.childNodes[i], null, true); 6433 if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) { 6434 var cOff = _currentInstance.getOffset(cEl); 6435 _delements[id][cid] = { 6436 id: cid, 6437 offset: { 6438 left: cOff.left - parentOffset.left, 6439 top: cOff.top - parentOffset.top 6440 } 6441 }; 6442 _draggablesForElements[cid] = id; 6443 } 6444 _oneLevel(p.childNodes[i]); 6445 } 6446 } 6447 } 6448 }; 6449 6450 _oneLevel(el); 6451 }; 6452 6453 // refresh the offsets for child elements of this element. 6454 this.updateOffsets = function (elId, childOffsetOverrides) { 6455 if (elId != null) { 6456 childOffsetOverrides = childOffsetOverrides || {}; 6457 var domEl = jsPlumb.getElement(elId), 6458 id = _currentInstance.getId(domEl), 6459 children = _delements[id], 6460 parentOffset = _currentInstance.getOffset(domEl); 6461 6462 if (children) { 6463 for (var i in children) { 6464 if (children.hasOwnProperty(i)) { 6465 var cel = jsPlumb.getElement(i), 6466 cOff = childOffsetOverrides[i] || _currentInstance.getOffset(cel); 6467 6468 // do not update if we have a value already and we'd just be writing 0,0 6469 if (cel.offsetParent == null && _delements[id][i] != null) { 6470 continue; 6471 } 6472 6473 _delements[id][i] = { 6474 id: i, 6475 offset: { 6476 left: cOff.left - parentOffset.left, 6477 top: cOff.top - parentOffset.top 6478 } 6479 }; 6480 _draggablesForElements[i] = id; 6481 } 6482 } 6483 } 6484 } 6485 }; 6486 6487 /** 6488 notification that an endpoint was added to the given el. we go up from that el's parent 6489 node, looking for a parent that has been registered as a draggable. if we find one, we add this 6490 el to that parent's list of elements to update on drag (if it is not there already) 6491 */ 6492 this.endpointAdded = function (el, id) { 6493 6494 id = id || _currentInstance.getId(el); 6495 6496 var b = document.body, 6497 p = el.parentNode; 6498 6499 _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1; 6500 6501 while (p != null && p !== b) { 6502 var pid = _currentInstance.getId(p, null, true); 6503 if (pid && _draggables[pid]) { 6504 var pLoc = _currentInstance.getOffset(p); 6505 6506 if (_delements[pid][id] == null) { 6507 var cLoc = _currentInstance.getOffset(el); 6508 _delements[pid][id] = { 6509 id: id, 6510 offset: { 6511 left: cLoc.left - pLoc.left, 6512 top: cLoc.top - pLoc.top 6513 } 6514 }; 6515 _draggablesForElements[id] = pid; 6516 } 6517 break; 6518 } 6519 p = p.parentNode; 6520 } 6521 }; 6522 6523 this.endpointDeleted = function (endpoint) { 6524 if (_elementsWithEndpoints[endpoint.elementId]) { 6525 _elementsWithEndpoints[endpoint.elementId]--; 6526 if (_elementsWithEndpoints[endpoint.elementId] <= 0) { 6527 for (var i in _delements) { 6528 if (_delements.hasOwnProperty(i) && _delements[i]) { 6529 delete _delements[i][endpoint.elementId]; 6530 delete _draggablesForElements[endpoint.elementId]; 6531 } 6532 } 6533 } 6534 } 6535 }; 6536 6537 this.changeId = function (oldId, newId) { 6538 _delements[newId] = _delements[oldId]; 6539 _delements[oldId] = {}; 6540 _draggablesForElements[newId] = _draggablesForElements[oldId]; 6541 _draggablesForElements[oldId] = null; 6542 }; 6543 6544 this.getElementsForDraggable = function (id) { 6545 return _delements[id]; 6546 }; 6547 6548 this.elementRemoved = function (elementId) { 6549 var elId = _draggablesForElements[elementId]; 6550 if (elId) { 6551 delete _delements[elId][elementId]; 6552 delete _draggablesForElements[elementId]; 6553 } 6554 }; 6555 6556 this.reset = function () { 6557 _draggables = {}; 6558 _dlist = []; 6559 _delements = {}; 6560 _elementsWithEndpoints = {}; 6561 }; 6562 6563 // 6564 // notification drag ended. We check automatically if need to update some 6565 // ancestor's offsets. 6566 // 6567 this.dragEnded = function (el) { 6568 if (el.offsetParent != null) { 6569 var id = _currentInstance.getId(el), 6570 ancestor = _draggablesForElements[id]; 6571 6572 if (ancestor) { 6573 this.updateOffsets(ancestor); 6574 } 6575 } 6576 }; 6577 6578 this.setParent = function (el, elId, p, pId, currentChildLocation) { 6579 var current = _draggablesForElements[elId]; 6580 if (!_delements[pId]) { 6581 _delements[pId] = {}; 6582 } 6583 var pLoc = _currentInstance.getOffset(p), 6584 cLoc = currentChildLocation || _currentInstance.getOffset(el); 6585 6586 if (current && _delements[current]) { 6587 delete _delements[current][elId]; 6588 } 6589 6590 _delements[pId][elId] = { 6591 id:elId, 6592 offset : { 6593 left: cLoc.left - pLoc.left, 6594 top: cLoc.top - pLoc.top 6595 } 6596 }; 6597 _draggablesForElements[elId] = pId; 6598 }; 6599 6600 this.clearParent = function(el, elId) { 6601 var current = _draggablesForElements[elId]; 6602 if (current) { 6603 delete _delements[current][elId]; 6604 delete _draggablesForElements[elId]; 6605 } 6606 }; 6607 6608 this.revalidateParent = function(el, elId, childOffset) { 6609 var current = _draggablesForElements[elId]; 6610 if (current) { 6611 var co = {}; 6612 co[elId] = childOffset; 6613 this.updateOffsets(current, co); 6614 _currentInstance.revalidate(current); 6615 } 6616 }; 6617 6618 this.getDragAncestor = function (el) { 6619 var de = jsPlumb.getElement(el), 6620 id = _currentInstance.getId(de), 6621 aid = _draggablesForElements[id]; 6622 6623 if (aid) { 6624 return jsPlumb.getElement(aid); 6625 } 6626 else { 6627 return null; 6628 } 6629 }; 6630 6631 }; 6632 6633 var trim = function (str) { 6634 return str == null ? null : (str.replace(/^\s\s*/, '').replace(/\s\s*$/, '')); 6635 }, 6636 _setClassName = function (el, cn) { 6637 cn = trim(cn); 6638 if (typeof el.className.baseVal !== "undefined") { 6639 el.className.baseVal = cn; 6640 } 6641 else { 6642 el.className = cn; 6643 } 6644 }, 6645 _getClassName = function (el) { 6646 return (typeof el.className.baseVal === "undefined") ? el.className : el.className.baseVal; 6647 }, 6648 _classManip = function (el, classesToAdd, classesToRemove) { 6649 classesToAdd = classesToAdd == null ? [] : _ju.isArray(classesToAdd) ? classesToAdd : classesToAdd.split(/\s+/); 6650 classesToRemove = classesToRemove == null ? [] : _ju.isArray(classesToRemove) ? classesToRemove : classesToRemove.split(/\s+/); 6651 6652 var className = _getClassName(el), 6653 curClasses = className.split(/\s+/); 6654 6655 var _oneSet = function (add, classes) { 6656 for (var i = 0; i < classes.length; i++) { 6657 if (add) { 6658 if (curClasses.indexOf(classes[i]) === -1) { 6659 curClasses.push(classes[i]); 6660 } 6661 } 6662 else { 6663 var idx = curClasses.indexOf(classes[i]); 6664 if (idx !== -1) { 6665 curClasses.splice(idx, 1); 6666 } 6667 } 6668 } 6669 }; 6670 6671 _oneSet(true, classesToAdd); 6672 _oneSet(false, classesToRemove); 6673 6674 _setClassName(el, curClasses.join(" ")); 6675 }; 6676 6677 root.jsPlumb.extend(root.jsPlumbInstance.prototype, { 6678 6679 headless: false, 6680 6681 pageLocation: _pageLocation, 6682 screenLocation: _screenLocation, 6683 clientLocation: _clientLocation, 6684 6685 getDragManager:function() { 6686 if (this.dragManager == null) { 6687 this.dragManager = new DragManager(this); 6688 } 6689 6690 return this.dragManager; 6691 }, 6692 6693 recalculateOffsets:function(elId) { 6694 this.getDragManager().updateOffsets(elId); 6695 }, 6696 6697 createElement:function(tag, style, clazz, atts) { 6698 return this.createElementNS(null, tag, style, clazz, atts); 6699 }, 6700 6701 createElementNS:function(ns, tag, style, clazz, atts) { 6702 var e = ns == null ? document.createElement(tag) : document.createElementNS(ns, tag); 6703 var i; 6704 style = style || {}; 6705 for (i in style) { 6706 e.style[i] = style[i]; 6707 } 6708 6709 if (clazz) { 6710 e.className = clazz; 6711 } 6712 6713 atts = atts || {}; 6714 for (i in atts) { 6715 e.setAttribute(i, "" + atts[i]); 6716 } 6717 6718 return e; 6719 }, 6720 6721 getAttribute: function (el, attName) { 6722 return el.getAttribute != null ? el.getAttribute(attName) : null; 6723 }, 6724 6725 setAttribute: function (el, a, v) { 6726 if (el.setAttribute != null) { 6727 el.setAttribute(a, v); 6728 } 6729 }, 6730 6731 setAttributes: function (el, atts) { 6732 for (var i in atts) { 6733 if (atts.hasOwnProperty(i)) { 6734 el.setAttribute(i, atts[i]); 6735 } 6736 } 6737 }, 6738 appendToRoot: function (node) { 6739 document.body.appendChild(node); 6740 }, 6741 getRenderModes: function () { 6742 return [ "svg" ]; 6743 }, 6744 getClass:_getClassName, 6745 addClass: function (el, clazz) { 6746 jsPlumb.each(el, function (e) { 6747 _classManip(e, clazz); 6748 }); 6749 }, 6750 hasClass: function (el, clazz) { 6751 el = jsPlumb.getElement(el); 6752 if (el.classList) { 6753 return el.classList.contains(clazz); 6754 } 6755 else { 6756 return _getClassName(el).indexOf(clazz) !== -1; 6757 } 6758 }, 6759 removeClass: function (el, clazz) { 6760 jsPlumb.each(el, function (e) { 6761 _classManip(e, null, clazz); 6762 }); 6763 }, 6764 updateClasses: function (el, toAdd, toRemove) { 6765 jsPlumb.each(el, function (e) { 6766 _classManip(e, toAdd, toRemove); 6767 }); 6768 }, 6769 setClass: function (el, clazz) { 6770 jsPlumb.each(el, function (e) { 6771 _setClassName(e, clazz); 6772 }); 6773 }, 6774 setPosition: function (el, p) { 6775 el.style.left = p.left + "px"; 6776 el.style.top = p.top + "px"; 6777 }, 6778 getPosition: function (el) { 6779 var _one = function (prop) { 6780 var v = el.style[prop]; 6781 return v ? v.substring(0, v.length - 2) : 0; 6782 }; 6783 return { 6784 left: _one("left"), 6785 top: _one("top") 6786 }; 6787 }, 6788 getStyle:function(el, prop) { 6789 if (typeof window.getComputedStyle !== 'undefined') { 6790 return getComputedStyle(el, null).getPropertyValue(prop); 6791 } else { 6792 return el.currentStyle[prop]; 6793 } 6794 }, 6795 getSelector: function (ctx, spec) { 6796 var sel = null; 6797 if (arguments.length === 1) { 6798 sel = ctx.nodeType != null ? ctx : document.querySelectorAll(ctx); 6799 } 6800 else { 6801 sel = ctx.querySelectorAll(spec); 6802 } 6803 6804 return sel; 6805 }, 6806 getOffset:function(el, relativeToRoot, container) { 6807 el = jsPlumb.getElement(el); 6808 container = container || this.getContainer(); 6809 var out = { 6810 left: el.offsetLeft, 6811 top: el.offsetTop 6812 }, 6813 op = (relativeToRoot || (container != null && (el !== container && el.offsetParent !== container))) ? el.offsetParent : null, 6814 _maybeAdjustScroll = function(offsetParent) { 6815 if (offsetParent != null && offsetParent !== document.body && (offsetParent.scrollTop > 0 || offsetParent.scrollLeft > 0)) { 6816 out.left -= offsetParent.scrollLeft; 6817 out.top -= offsetParent.scrollTop; 6818 } 6819 }.bind(this); 6820 6821 while (op != null) { 6822 out.left += op.offsetLeft; 6823 out.top += op.offsetTop; 6824 _maybeAdjustScroll(op); 6825 op = relativeToRoot ? op.offsetParent : 6826 op.offsetParent === container ? null : op.offsetParent; 6827 } 6828 6829 // if container is scrolled and the element (or its offset parent) is not absolute or fixed, adjust accordingly. 6830 if (container != null && !relativeToRoot && (container.scrollTop > 0 || container.scrollLeft > 0)) { 6831 var pp = el.offsetParent != null ? this.getStyle(el.offsetParent, "position") : "static", 6832 p = this.getStyle(el, "position"); 6833 if (p !== "absolute" && p !== "fixed" && pp !== "absolute" && pp !== "fixed") { 6834 out.left -= container.scrollLeft; 6835 out.top -= container.scrollTop; 6836 } 6837 } 6838 return out; 6839 }, 6840 // 6841 // return x+y proportion of the given element's size corresponding to the location of the given event. 6842 // 6843 getPositionOnElement: function (evt, el, zoom) { 6844 var box = typeof el.getBoundingClientRect !== "undefined" ? el.getBoundingClientRect() : { left: 0, top: 0, width: 0, height: 0 }, 6845 body = document.body, 6846 docElem = document.documentElement, 6847 scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop, 6848 scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft, 6849 clientTop = docElem.clientTop || body.clientTop || 0, 6850 clientLeft = docElem.clientLeft || body.clientLeft || 0, 6851 pst = 0, 6852 psl = 0, 6853 top = box.top + scrollTop - clientTop + (pst * zoom), 6854 left = box.left + scrollLeft - clientLeft + (psl * zoom), 6855 cl = jsPlumb.pageLocation(evt), 6856 w = box.width || (el.offsetWidth * zoom), 6857 h = box.height || (el.offsetHeight * zoom), 6858 x = (cl[0] - left) / w, 6859 y = (cl[1] - top) / h; 6860 6861 return [ x, y ]; 6862 }, 6863 6864 /** 6865 * Gets the absolute position of some element as read from the left/top properties in its style. 6866 * @method getAbsolutePosition 6867 * @param {Element} el The element to retrieve the absolute coordinates from. **Note** this is a DOM element, not a selector from the underlying library. 6868 * @return {Number[]} [left, top] pixel values. 6869 */ 6870 getAbsolutePosition: function (el) { 6871 var _one = function (s) { 6872 var ss = el.style[s]; 6873 if (ss) { 6874 return parseFloat(ss.substring(0, ss.length - 2)); 6875 } 6876 }; 6877 return [ _one("left"), _one("top") ]; 6878 }, 6879 6880 /** 6881 * Sets the absolute position of some element by setting the left/top properties in its style. 6882 * @method setAbsolutePosition 6883 * @param {Element} el The element to set the absolute coordinates on. **Note** this is a DOM element, not a selector from the underlying library. 6884 * @param {Number[]} xy x and y coordinates 6885 * @param {Number[]} [animateFrom] Optional previous xy to animate from. 6886 * @param {Object} [animateOptions] Options for the animation. 6887 */ 6888 setAbsolutePosition: function (el, xy, animateFrom, animateOptions) { 6889 if (animateFrom) { 6890 this.animate(el, { 6891 left: "+=" + (xy[0] - animateFrom[0]), 6892 top: "+=" + (xy[1] - animateFrom[1]) 6893 }, animateOptions); 6894 } 6895 else { 6896 el.style.left = xy[0] + "px"; 6897 el.style.top = xy[1] + "px"; 6898 } 6899 }, 6900 /** 6901 * gets the size for the element, in an array : [ width, height ]. 6902 */ 6903 getSize: function (el) { 6904 return [ el.offsetWidth, el.offsetHeight ]; 6905 }, 6906 getWidth: function (el) { 6907 return el.offsetWidth; 6908 }, 6909 getHeight: function (el) { 6910 return el.offsetHeight; 6911 }, 6912 getRenderMode : function() { return "svg"; } 6913 6914 }); 6915 }).call(typeof window !== 'undefined' ? window : this); 6916 6917 /* 6918 * jsPlumb Community Edition 6919 * 6920 * Provides a way to visually connect elements on an HTML page, using SVG. 6921 * 6922 * This file contains code for components that support overlays. 6923 * 6924 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 6925 * 6926 * https://jsplumbtoolkit.com 6927 * https://github.com/jsplumb/jsplumb 6928 * 6929 * Dual licensed under the MIT and GPL2 licenses. 6930 */ 6931 ;(function() { 6932 6933 "use strict"; 6934 var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil; 6935 6936 // ------------------------------ BEGIN OverlayCapablejsPlumbUIComponent -------------------------------------------- 6937 6938 var _internalLabelOverlayId = "__label", 6939 // this is a shortcut helper method to let people add a label as 6940 // overlay. 6941 _makeLabelOverlay = function (component, params) { 6942 6943 var _params = { 6944 cssClass: params.cssClass, 6945 labelStyle: component.labelStyle, 6946 id: _internalLabelOverlayId, 6947 component: component, 6948 _jsPlumb: component._jsPlumb.instance // TODO not necessary, since the instance can be accessed through the component. 6949 }, 6950 mergedParams = _jp.extend(_params, params); 6951 6952 return new _jp.Overlays[component._jsPlumb.instance.getRenderMode()].Label(mergedParams); 6953 }, 6954 _processOverlay = function (component, o) { 6955 var _newOverlay = null; 6956 if (_ju.isArray(o)) { // this is for the shorthand ["Arrow", { width:50 }] syntax 6957 // there's also a three arg version: 6958 // ["Arrow", { width:50 }, {location:0.7}] 6959 // which merges the 3rd arg into the 2nd. 6960 var type = o[0], 6961 // make a copy of the object so as not to mess up anyone else's reference... 6962 p = _jp.extend({component: component, _jsPlumb: component._jsPlumb.instance}, o[1]); 6963 if (o.length === 3) { 6964 _jp.extend(p, o[2]); 6965 } 6966 _newOverlay = new _jp.Overlays[component._jsPlumb.instance.getRenderMode()][type](p); 6967 } else if (o.constructor === String) { 6968 _newOverlay = new _jp.Overlays[component._jsPlumb.instance.getRenderMode()][o]({component: component, _jsPlumb: component._jsPlumb.instance}); 6969 } else { 6970 _newOverlay = o; 6971 } 6972 6973 _newOverlay.id = _newOverlay.id || _ju.uuid(); 6974 component.cacheTypeItem("overlay", _newOverlay, _newOverlay.id); 6975 component._jsPlumb.overlays[_newOverlay.id] = _newOverlay; 6976 6977 return _newOverlay; 6978 }; 6979 6980 _jp.OverlayCapableJsPlumbUIComponent = function (params) { 6981 6982 root.jsPlumbUIComponent.apply(this, arguments); 6983 this._jsPlumb.overlays = {}; 6984 this._jsPlumb.overlayPositions = {}; 6985 6986 if (params.label) { 6987 this.getDefaultType().overlays[_internalLabelOverlayId] = ["Label", { 6988 label: params.label, 6989 location: params.labelLocation || this.defaultLabelLocation || 0.5, 6990 labelStyle: params.labelStyle || this._jsPlumb.instance.Defaults.LabelStyle, 6991 id:_internalLabelOverlayId 6992 }]; 6993 } 6994 6995 this.setListenerComponent = function (c) { 6996 if (this._jsPlumb) { 6997 for (var i in this._jsPlumb.overlays) { 6998 this._jsPlumb.overlays[i].setListenerComponent(c); 6999 } 7000 } 7001 }; 7002 }; 7003 7004 _jp.OverlayCapableJsPlumbUIComponent.applyType = function (component, t) { 7005 if (t.overlays) { 7006 // loop through the ones in the type. if already present on the component, 7007 // dont remove or re-add. 7008 var keep = {}, i; 7009 7010 for (i in t.overlays) { 7011 7012 var existing = component._jsPlumb.overlays[t.overlays[i][1].id]; 7013 if (existing) { 7014 // maybe update from data, if there were parameterised values for instance. 7015 existing.updateFrom(t.overlays[i][1]); 7016 keep[t.overlays[i][1].id] = true; 7017 } 7018 else { 7019 var c = component.getCachedTypeItem("overlay", t.overlays[i][1].id); 7020 if (c != null) { 7021 c.reattach(component._jsPlumb.instance); 7022 c.setVisible(true); 7023 // maybe update from data, if there were parameterised values for instance. 7024 c.updateFrom(t.overlays[i][1]); 7025 component._jsPlumb.overlays[c.id] = c; 7026 } 7027 else { 7028 c = component.addOverlay(t.overlays[i], true); 7029 } 7030 keep[c.id] = true; 7031 } 7032 } 7033 7034 // now loop through the full overlays and remove those that we dont want to keep 7035 for (i in component._jsPlumb.overlays) { 7036 if (keep[component._jsPlumb.overlays[i].id] == null) { 7037 component.removeOverlay(component._jsPlumb.overlays[i].id, true); // remove overlay but dont clean it up. 7038 // that would remove event listeners etc; overlays are never discarded by the types stuff, they are 7039 // just detached/reattached. 7040 } 7041 } 7042 } 7043 }; 7044 7045 _ju.extend(_jp.OverlayCapableJsPlumbUIComponent, root.jsPlumbUIComponent, { 7046 7047 setHover: function (hover, ignoreAttachedElements) { 7048 if (this._jsPlumb && !this._jsPlumb.instance.isConnectionBeingDragged()) { 7049 for (var i in this._jsPlumb.overlays) { 7050 this._jsPlumb.overlays[i][hover ? "addClass" : "removeClass"](this._jsPlumb.instance.hoverClass); 7051 } 7052 } 7053 }, 7054 addOverlay: function (overlay, doNotRepaint) { 7055 var o = _processOverlay(this, overlay); 7056 if (!doNotRepaint) { 7057 this.repaint(); 7058 } 7059 return o; 7060 }, 7061 getOverlay: function (id) { 7062 return this._jsPlumb.overlays[id]; 7063 }, 7064 getOverlays: function () { 7065 return this._jsPlumb.overlays; 7066 }, 7067 hideOverlay: function (id) { 7068 var o = this.getOverlay(id); 7069 if (o) { 7070 o.hide(); 7071 } 7072 }, 7073 hideOverlays: function () { 7074 for (var i in this._jsPlumb.overlays) { 7075 this._jsPlumb.overlays[i].hide(); 7076 } 7077 }, 7078 showOverlay: function (id) { 7079 var o = this.getOverlay(id); 7080 if (o) { 7081 o.show(); 7082 } 7083 }, 7084 showOverlays: function () { 7085 for (var i in this._jsPlumb.overlays) { 7086 this._jsPlumb.overlays[i].show(); 7087 } 7088 }, 7089 removeAllOverlays: function (doNotRepaint) { 7090 for (var i in this._jsPlumb.overlays) { 7091 if (this._jsPlumb.overlays[i].cleanup) { 7092 this._jsPlumb.overlays[i].cleanup(); 7093 } 7094 } 7095 7096 this._jsPlumb.overlays = {}; 7097 this._jsPlumb.overlayPositions = null; 7098 if (!doNotRepaint) { 7099 this.repaint(); 7100 } 7101 }, 7102 removeOverlay: function (overlayId, dontCleanup) { 7103 var o = this._jsPlumb.overlays[overlayId]; 7104 if (o) { 7105 o.setVisible(false); 7106 if (!dontCleanup && o.cleanup) { 7107 o.cleanup(); 7108 } 7109 delete this._jsPlumb.overlays[overlayId]; 7110 if (this._jsPlumb.overlayPositions) { 7111 delete this._jsPlumb.overlayPositions[overlayId]; 7112 } 7113 } 7114 }, 7115 removeOverlays: function () { 7116 for (var i = 0, j = arguments.length; i < j; i++) { 7117 this.removeOverlay(arguments[i]); 7118 } 7119 }, 7120 moveParent: function (newParent) { 7121 if (this.bgCanvas) { 7122 this.bgCanvas.parentNode.removeChild(this.bgCanvas); 7123 newParent.appendChild(this.bgCanvas); 7124 } 7125 7126 if (this.canvas && this.canvas.parentNode) { 7127 this.canvas.parentNode.removeChild(this.canvas); 7128 newParent.appendChild(this.canvas); 7129 7130 for (var i in this._jsPlumb.overlays) { 7131 if (this._jsPlumb.overlays[i].isAppendedAtTopLevel) { 7132 var el = this._jsPlumb.overlays[i].getElement(); 7133 el.parentNode.removeChild(el); 7134 newParent.appendChild(el); 7135 } 7136 } 7137 } 7138 }, 7139 getLabel: function () { 7140 var lo = this.getOverlay(_internalLabelOverlayId); 7141 return lo != null ? lo.getLabel() : null; 7142 }, 7143 getLabelOverlay: function () { 7144 return this.getOverlay(_internalLabelOverlayId); 7145 }, 7146 setLabel: function (l) { 7147 var lo = this.getOverlay(_internalLabelOverlayId); 7148 if (!lo) { 7149 var params = l.constructor === String || l.constructor === Function ? { label: l } : l; 7150 lo = _makeLabelOverlay(this, params); 7151 this._jsPlumb.overlays[_internalLabelOverlayId] = lo; 7152 } 7153 else { 7154 if (l.constructor === String || l.constructor === Function) { 7155 lo.setLabel(l); 7156 } 7157 else { 7158 if (l.label) { 7159 lo.setLabel(l.label); 7160 } 7161 if (l.location) { 7162 lo.setLocation(l.location); 7163 } 7164 } 7165 } 7166 7167 if (!this._jsPlumb.instance.isSuspendDrawing()) { 7168 this.repaint(); 7169 } 7170 }, 7171 cleanup: function (force) { 7172 for (var i in this._jsPlumb.overlays) { 7173 this._jsPlumb.overlays[i].cleanup(force); 7174 this._jsPlumb.overlays[i].destroy(force); 7175 } 7176 if (force) { 7177 this._jsPlumb.overlays = {}; 7178 this._jsPlumb.overlayPositions = null; 7179 } 7180 }, 7181 setVisible: function (v) { 7182 this[v ? "showOverlays" : "hideOverlays"](); 7183 }, 7184 setAbsoluteOverlayPosition: function (overlay, xy) { 7185 this._jsPlumb.overlayPositions[overlay.id] = xy; 7186 }, 7187 getAbsoluteOverlayPosition: function (overlay) { 7188 return this._jsPlumb.overlayPositions ? this._jsPlumb.overlayPositions[overlay.id] : null; 7189 }, 7190 _clazzManip:function(action, clazz, dontUpdateOverlays) { 7191 if (!dontUpdateOverlays) { 7192 for (var i in this._jsPlumb.overlays) { 7193 this._jsPlumb.overlays[i][action + "Class"](clazz); 7194 } 7195 } 7196 }, 7197 addClass:function(clazz, dontUpdateOverlays) { 7198 this._clazzManip("add", clazz, dontUpdateOverlays); 7199 }, 7200 removeClass:function(clazz, dontUpdateOverlays) { 7201 this._clazzManip("remove", clazz, dontUpdateOverlays); 7202 } 7203 }); 7204 7205 // ------------------------------ END OverlayCapablejsPlumbUIComponent -------------------------------------------- 7206 7207 }).call(typeof window !== 'undefined' ? window : this); 7208 7209 /* 7210 * jsPlumb Community Edition 7211 * 7212 * Provides a way to visually connect elements on an HTML page, using SVG. 7213 * 7214 * This file contains the code for Endpoints. 7215 * 7216 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 7217 * 7218 * https://jsplumbtoolkit.com 7219 * https://github.com/jsplumb/jsplumb 7220 * 7221 * Dual licensed under the MIT and GPL2 licenses. 7222 */ 7223 (function () { 7224 7225 "use strict"; 7226 var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil; 7227 7228 // create the drag handler for a connection 7229 var _makeConnectionDragHandler = function (endpoint, placeholder, _jsPlumb) { 7230 var stopped = false; 7231 return { 7232 drag: function () { 7233 if (stopped) { 7234 stopped = false; 7235 return true; 7236 } 7237 7238 if (placeholder.element) { 7239 var _ui = _jsPlumb.getUIPosition(arguments, _jsPlumb.getZoom()); 7240 if (_ui != null) { 7241 _jsPlumb.setPosition(placeholder.element, _ui); 7242 } 7243 _jsPlumb.repaint(placeholder.element, _ui); 7244 // always repaint the source endpoint, because only continuous/dynamic anchors cause the endpoint 7245 // to be repainted, so static anchors need to be told (or the endpoint gets dragged around) 7246 endpoint.paint({anchorPoint:endpoint.anchor.getCurrentLocation({element:endpoint})}); 7247 } 7248 }, 7249 stopDrag: function () { 7250 stopped = true; 7251 } 7252 }; 7253 }; 7254 7255 // creates a placeholder div for dragging purposes, adds it, and pre-computes its offset. 7256 var _makeDraggablePlaceholder = function (placeholder, _jsPlumb, ipco, ips) { 7257 var n = _jsPlumb.createElement("div", { position : "absolute" }); 7258 _jsPlumb.appendElement(n); 7259 var id = _jsPlumb.getId(n); 7260 _jsPlumb.setPosition(n, ipco); 7261 n.style.width = ips[0] + "px"; 7262 n.style.height = ips[1] + "px"; 7263 _jsPlumb.manage(id, n, true); // TRANSIENT MANAGE 7264 // create and assign an id, and initialize the offset. 7265 placeholder.id = id; 7266 placeholder.element = n; 7267 }; 7268 7269 // create a floating endpoint (for drag connections) 7270 var _makeFloatingEndpoint = function (paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement, _jsPlumb, _newEndpoint, scope) { 7271 var floatingAnchor = new _jp.FloatingAnchor({ reference: referenceAnchor, referenceCanvas: referenceCanvas, jsPlumbInstance: _jsPlumb }); 7272 //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not 7273 // adding the floating endpoint as a droppable. that makes more sense anyway! 7274 // TRANSIENT MANAGE 7275 return _newEndpoint({ 7276 paintStyle: paintStyle, 7277 endpoint: endpoint, 7278 anchor: floatingAnchor, 7279 source: sourceElement, 7280 scope: scope 7281 }); 7282 }; 7283 7284 var typeParameters = [ "connectorStyle", "connectorHoverStyle", "connectorOverlays", 7285 "connector", "connectionType", "connectorClass", "connectorHoverClass" ]; 7286 7287 // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, 7288 // or no connection to it is found, we return the first connection in our list. 7289 var findConnectionToUseForDynamicAnchor = function (ep, elementWithPrecedence) { 7290 var idx = 0; 7291 if (elementWithPrecedence != null) { 7292 for (var i = 0; i < ep.connections.length; i++) { 7293 if (ep.connections[i].sourceId === elementWithPrecedence || ep.connections[i].targetId === elementWithPrecedence) { 7294 idx = i; 7295 break; 7296 } 7297 } 7298 } 7299 7300 return ep.connections[idx]; 7301 }; 7302 7303 _jp.Endpoint = function (params) { 7304 var _jsPlumb = params._jsPlumb, 7305 _newConnection = params.newConnection, 7306 _newEndpoint = params.newEndpoint; 7307 7308 this.idPrefix = "_jsplumb_e_"; 7309 this.defaultLabelLocation = [ 0.5, 0.5 ]; 7310 this.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; 7311 _jp.OverlayCapableJsPlumbUIComponent.apply(this, arguments); 7312 7313 // TYPE 7314 7315 this.appendToDefaultType({ 7316 connectionType:params.connectionType, 7317 maxConnections: params.maxConnections == null ? this._jsPlumb.instance.Defaults.MaxConnections : params.maxConnections, // maximum number of connections this endpoint can be the source of., 7318 paintStyle: params.endpointStyle || params.paintStyle || params.style || this._jsPlumb.instance.Defaults.EndpointStyle || _jp.Defaults.EndpointStyle, 7319 hoverPaintStyle: params.endpointHoverStyle || params.hoverPaintStyle || this._jsPlumb.instance.Defaults.EndpointHoverStyle || _jp.Defaults.EndpointHoverStyle, 7320 connectorStyle: params.connectorStyle, 7321 connectorHoverStyle: params.connectorHoverStyle, 7322 connectorClass: params.connectorClass, 7323 connectorHoverClass: params.connectorHoverClass, 7324 connectorOverlays: params.connectorOverlays, 7325 connector: params.connector, 7326 connectorTooltip: params.connectorTooltip 7327 }); 7328 7329 // END TYPE 7330 7331 this._jsPlumb.enabled = !(params.enabled === false); 7332 this._jsPlumb.visible = true; 7333 this.element = _jp.getElement(params.source); 7334 this._jsPlumb.uuid = params.uuid; 7335 this._jsPlumb.floatingEndpoint = null; 7336 var inPlaceCopy = null; 7337 if (this._jsPlumb.uuid) { 7338 params.endpointsByUUID[this._jsPlumb.uuid] = this; 7339 } 7340 this.elementId = params.elementId; 7341 this.dragProxy = params.dragProxy; 7342 7343 this._jsPlumb.connectionCost = params.connectionCost; 7344 this._jsPlumb.connectionsDirected = params.connectionsDirected; 7345 this._jsPlumb.currentAnchorClass = ""; 7346 this._jsPlumb.events = {}; 7347 7348 var deleteOnEmpty = params.deleteOnEmpty === true; 7349 this.setDeleteOnEmpty = function(d) { deleteOnEmpty = d; }; 7350 7351 var _updateAnchorClass = function () { 7352 // stash old, get new 7353 var oldAnchorClass = _jsPlumb.endpointAnchorClassPrefix + "-" + this._jsPlumb.currentAnchorClass; 7354 this._jsPlumb.currentAnchorClass = this.anchor.getCssClass(); 7355 var anchorClass = _jsPlumb.endpointAnchorClassPrefix + (this._jsPlumb.currentAnchorClass ? "-" + this._jsPlumb.currentAnchorClass : ""); 7356 7357 this.removeClass(oldAnchorClass); 7358 this.addClass(anchorClass); 7359 // add and remove at the same time to reduce the number of reflows. 7360 _jp.updateClasses(this.element, anchorClass, oldAnchorClass); 7361 }.bind(this); 7362 7363 this.prepareAnchor = function(anchorParams) { 7364 var a = this._jsPlumb.instance.makeAnchor(anchorParams, this.elementId, _jsPlumb); 7365 a.bind("anchorChanged", function (currentAnchor) { 7366 this.fire("anchorChanged", {endpoint: this, anchor: currentAnchor}); 7367 _updateAnchorClass(); 7368 }.bind(this)); 7369 return a; 7370 }; 7371 7372 this.setPreparedAnchor = function(anchor, doNotRepaint) { 7373 this._jsPlumb.instance.continuousAnchorFactory.clear(this.elementId); 7374 this.anchor = anchor; 7375 _updateAnchorClass(); 7376 7377 if (!doNotRepaint) { 7378 this._jsPlumb.instance.repaint(this.elementId); 7379 } 7380 7381 return this; 7382 }; 7383 7384 this.setAnchor = function (anchorParams, doNotRepaint) { 7385 var a = this.prepareAnchor(anchorParams); 7386 this.setPreparedAnchor(a, doNotRepaint); 7387 return this; 7388 }; 7389 7390 var internalHover = function (state) { 7391 if (this.connections.length > 0) { 7392 for (var i = 0; i < this.connections.length; i++) { 7393 this.connections[i].setHover(state, false); 7394 } 7395 } 7396 else { 7397 this.setHover(state); 7398 } 7399 }.bind(this); 7400 7401 this.bind("mouseover", function () { 7402 internalHover(true); 7403 }); 7404 this.bind("mouseout", function () { 7405 internalHover(false); 7406 }); 7407 7408 // ANCHOR MANAGER 7409 if (!params._transient) { // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. 7410 this._jsPlumb.instance.anchorManager.add(this, this.elementId); 7411 } 7412 7413 this.prepareEndpoint = function(ep, typeId) { 7414 var _e = function (t, p) { 7415 var rm = _jsPlumb.getRenderMode(); 7416 if (_jp.Endpoints[rm][t]) { 7417 return new _jp.Endpoints[rm][t](p); 7418 } 7419 if (!_jsPlumb.Defaults.DoNotThrowErrors) { 7420 throw { msg: "jsPlumb: unknown endpoint type '" + t + "'" }; 7421 } 7422 }; 7423 7424 var endpointArgs = { 7425 _jsPlumb: this._jsPlumb.instance, 7426 cssClass: params.cssClass, 7427 container: params.container, 7428 tooltip: params.tooltip, 7429 connectorTooltip: params.connectorTooltip, 7430 endpoint: this 7431 }; 7432 7433 var endpoint; 7434 7435 if (_ju.isString(ep)) { 7436 endpoint = _e(ep, endpointArgs); 7437 } 7438 else if (_ju.isArray(ep)) { 7439 endpointArgs = _ju.merge(ep[1], endpointArgs); 7440 endpoint = _e(ep[0], endpointArgs); 7441 } 7442 else { 7443 endpoint = ep.clone(); 7444 } 7445 7446 // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, 7447 // and the clone is left in its place while the original one goes off on a magical journey. 7448 // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by 7449 // the whole world. 7450 //var argsForClone = jsPlumb.extend({}, endpointArgs); 7451 endpoint.clone = function () { 7452 // TODO this, and the code above, can be refactored to be more dry. 7453 if (_ju.isString(ep)) { 7454 return _e(ep, endpointArgs); 7455 } 7456 else if (_ju.isArray(ep)) { 7457 endpointArgs = _ju.merge(ep[1], endpointArgs); 7458 return _e(ep[0], endpointArgs); 7459 } 7460 }.bind(this); 7461 7462 endpoint.typeId = typeId; 7463 return endpoint; 7464 }; 7465 7466 this.setEndpoint = function(ep, doNotRepaint) { 7467 var _ep = this.prepareEndpoint(ep); 7468 this.setPreparedEndpoint(_ep, true); 7469 }; 7470 7471 this.setPreparedEndpoint = function (ep, doNotRepaint) { 7472 if (this.endpoint != null) { 7473 this.endpoint.cleanup(); 7474 this.endpoint.destroy(); 7475 } 7476 this.endpoint = ep; 7477 this.type = this.endpoint.type; 7478 this.canvas = this.endpoint.canvas; 7479 }; 7480 7481 _jp.extend(this, params, typeParameters); 7482 7483 this.isSource = params.isSource || false; 7484 this.isTemporarySource = params.isTemporarySource || false; 7485 this.isTarget = params.isTarget || false; 7486 7487 this.connections = params.connections || []; 7488 this.connectorPointerEvents = params["connector-pointer-events"]; 7489 7490 this.scope = params.scope || _jsPlumb.getDefaultScope(); 7491 this.timestamp = null; 7492 this.reattachConnections = params.reattach || _jsPlumb.Defaults.ReattachConnections; 7493 this.connectionsDetachable = _jsPlumb.Defaults.ConnectionsDetachable; 7494 if (params.connectionsDetachable === false || params.detachable === false) { 7495 this.connectionsDetachable = false; 7496 } 7497 this.dragAllowedWhenFull = params.dragAllowedWhenFull !== false; 7498 7499 if (params.onMaxConnections) { 7500 this.bind("maxConnections", params.onMaxConnections); 7501 } 7502 7503 // 7504 // add a connection. not part of public API. 7505 // 7506 this.addConnection = function (connection) { 7507 this.connections.push(connection); 7508 this[(this.connections.length > 0 ? "add" : "remove") + "Class"](_jsPlumb.endpointConnectedClass); 7509 this[(this.isFull() ? "add" : "remove") + "Class"](_jsPlumb.endpointFullClass); 7510 }; 7511 7512 this.detachFromConnection = function (connection, idx, doNotCleanup) { 7513 idx = idx == null ? this.connections.indexOf(connection) : idx; 7514 if (idx >= 0) { 7515 this.connections.splice(idx, 1); 7516 this[(this.connections.length > 0 ? "add" : "remove") + "Class"](_jsPlumb.endpointConnectedClass); 7517 this[(this.isFull() ? "add" : "remove") + "Class"](_jsPlumb.endpointFullClass); 7518 } 7519 7520 if (!doNotCleanup && deleteOnEmpty && this.connections.length === 0) { 7521 _jsPlumb.deleteObject({ 7522 endpoint: this, 7523 fireEvent: false, 7524 deleteAttachedObjects: doNotCleanup !== true 7525 }); 7526 } 7527 }; 7528 7529 this.deleteEveryConnection = function(params) { 7530 var c = this.connections.length; 7531 for (var i = 0; i < c; i++) { 7532 _jsPlumb.deleteConnection(this.connections[0], params); 7533 } 7534 }; 7535 7536 this.detachFrom = function (targetEndpoint, fireEvent, originalEvent) { 7537 var c = []; 7538 for (var i = 0; i < this.connections.length; i++) { 7539 if (this.connections[i].endpoints[1] === targetEndpoint || this.connections[i].endpoints[0] === targetEndpoint) { 7540 c.push(this.connections[i]); 7541 } 7542 } 7543 for (var j = 0, count = c.length; j < count; j++) { 7544 _jsPlumb.deleteConnection(c[0]); 7545 } 7546 return this; 7547 }; 7548 7549 this.getElement = function () { 7550 return this.element; 7551 }; 7552 7553 this.setElement = function (el) { 7554 var parentId = this._jsPlumb.instance.getId(el), 7555 curId = this.elementId; 7556 // remove the endpoint from the list for the current endpoint's element 7557 _ju.removeWithFunction(params.endpointsByElement[this.elementId], function (e) { 7558 return e.id === this.id; 7559 }.bind(this)); 7560 this.element = _jp.getElement(el); 7561 this.elementId = _jsPlumb.getId(this.element); 7562 _jsPlumb.anchorManager.rehomeEndpoint(this, curId, this.element); 7563 _jsPlumb.dragManager.endpointAdded(this.element); 7564 _ju.addToList(params.endpointsByElement, parentId, this); 7565 return this; 7566 }; 7567 7568 /** 7569 * private but must be exposed. 7570 */ 7571 this.makeInPlaceCopy = function () { 7572 var loc = this.anchor.getCurrentLocation({element: this}), 7573 o = this.anchor.getOrientation(this), 7574 acc = this.anchor.getCssClass(), 7575 inPlaceAnchor = { 7576 bind: function () { 7577 }, 7578 compute: function () { 7579 return [ loc[0], loc[1] ]; 7580 }, 7581 getCurrentLocation: function () { 7582 return [ loc[0], loc[1] ]; 7583 }, 7584 getOrientation: function () { 7585 return o; 7586 }, 7587 getCssClass: function () { 7588 return acc; 7589 } 7590 }; 7591 7592 return _newEndpoint({ 7593 dropOptions: params.dropOptions, 7594 anchor: inPlaceAnchor, 7595 source: this.element, 7596 paintStyle: this.getPaintStyle(), 7597 endpoint: params.hideOnDrag ? "Blank" : this.endpoint, 7598 _transient: true, 7599 scope: this.scope, 7600 reference:this 7601 }); 7602 }; 7603 7604 /** 7605 * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. 7606 */ 7607 this.connectorSelector = function () { 7608 var candidate = this.connections[0]; 7609 if (candidate) { 7610 return candidate; 7611 } 7612 else { 7613 return (this.connections.length < this._jsPlumb.maxConnections) || this._jsPlumb.maxConnections === -1 ? null : candidate; 7614 } 7615 }; 7616 7617 this.setStyle = this.setPaintStyle; 7618 7619 this.paint = function (params) { 7620 params = params || {}; 7621 var timestamp = params.timestamp, recalc = !(params.recalc === false); 7622 if (!timestamp || this.timestamp !== timestamp) { 7623 7624 var info = _jsPlumb.updateOffset({ elId: this.elementId, timestamp: timestamp }); 7625 7626 var xy = params.offset ? params.offset.o : info.o; 7627 if (xy != null) { 7628 var ap = params.anchorPoint, connectorPaintStyle = params.connectorPaintStyle; 7629 if (ap == null) { 7630 var wh = params.dimensions || info.s, 7631 anchorParams = { xy: [ xy.left, xy.top ], wh: wh, element: this, timestamp: timestamp }; 7632 if (recalc && this.anchor.isDynamic && this.connections.length > 0) { 7633 var c = findConnectionToUseForDynamicAnchor(this, params.elementWithPrecedence), 7634 oIdx = c.endpoints[0] === this ? 1 : 0, 7635 oId = oIdx === 0 ? c.sourceId : c.targetId, 7636 oInfo = _jsPlumb.getCachedData(oId), 7637 oOffset = oInfo.o, oWH = oInfo.s; 7638 anchorParams.txy = [ oOffset.left, oOffset.top ]; 7639 anchorParams.twh = oWH; 7640 anchorParams.tElement = c.endpoints[oIdx]; 7641 } 7642 ap = this.anchor.compute(anchorParams); 7643 } 7644 7645 this.endpoint.compute(ap, this.anchor.getOrientation(this), this._jsPlumb.paintStyleInUse, connectorPaintStyle || this.paintStyleInUse); 7646 this.endpoint.paint(this._jsPlumb.paintStyleInUse, this.anchor); 7647 this.timestamp = timestamp; 7648 7649 // paint overlays 7650 for (var i in this._jsPlumb.overlays) { 7651 if (this._jsPlumb.overlays.hasOwnProperty(i)) { 7652 var o = this._jsPlumb.overlays[i]; 7653 if (o.isVisible()) { 7654 this._jsPlumb.overlayPlacements[i] = o.draw(this.endpoint, this._jsPlumb.paintStyleInUse); 7655 o.paint(this._jsPlumb.overlayPlacements[i]); 7656 } 7657 } 7658 } 7659 } 7660 } 7661 }; 7662 7663 this.getTypeDescriptor = function () { 7664 return "endpoint"; 7665 }; 7666 this.isVisible = function () { 7667 return this._jsPlumb.visible; 7668 }; 7669 7670 this.repaint = this.paint; 7671 7672 var draggingInitialised = false; 7673 this.initDraggable = function () { 7674 7675 // is this a connection source? we make it draggable and have the 7676 // drag listener maintain a connection with a floating endpoint. 7677 if (!draggingInitialised && _jp.isDragSupported(this.element)) { 7678 var placeholderInfo = { id: null, element: null }, 7679 jpc = null, 7680 existingJpc = false, 7681 existingJpcParams = null, 7682 _dragHandler = _makeConnectionDragHandler(this, placeholderInfo, _jsPlumb), 7683 dragOptions = params.dragOptions || {}, 7684 defaultOpts = {}, 7685 startEvent = _jp.dragEvents.start, 7686 stopEvent = _jp.dragEvents.stop, 7687 dragEvent = _jp.dragEvents.drag, 7688 beforeStartEvent = _jp.dragEvents.beforeStart, 7689 payload; 7690 7691 // respond to beforeStart from katavorio; this will have, optionally, a payload of attribute values 7692 // that were placed there by the makeSource mousedown listener. 7693 var beforeStart = function(beforeStartParams) { 7694 payload = beforeStartParams.e.payload || {}; 7695 }; 7696 7697 var start = function (startParams) { 7698 7699 // ------------- first, get a connection to drag. this may be null, in which case we are dragging a new one. 7700 7701 jpc = this.connectorSelector(); 7702 7703 // -------------------------------- now a bunch of tests about whether or not to proceed ------------------------- 7704 7705 var _continue = true; 7706 // if not enabled, return 7707 if (!this.isEnabled()) { 7708 _continue = false; 7709 } 7710 // if no connection and we're not a source - or temporarily a source, as is the case with makeSource - return. 7711 if (jpc == null && !this.isSource && !this.isTemporarySource) { 7712 _continue = false; 7713 } 7714 // otherwise if we're full and not allowed to drag, also return false. 7715 if (this.isSource && this.isFull() && !(jpc != null && this.dragAllowedWhenFull)) { 7716 _continue = false; 7717 } 7718 // if the connection was setup as not detachable or one of its endpoints 7719 // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable 7720 // is set to false... 7721 if (jpc != null && !jpc.isDetachable(this)) { 7722 _continue = false; 7723 } 7724 7725 var beforeDrag = _jsPlumb.checkCondition(jpc == null ? "beforeDrag" : "beforeStartDetach", { 7726 endpoint:this, 7727 source:this.element, 7728 sourceId:this.elementId, 7729 connection:jpc 7730 }); 7731 if (beforeDrag === false) { 7732 _continue = false; 7733 } 7734 // else we might have been given some data. we'll pass it in to a new connection as 'data'. 7735 // here we also merge in the optional payload we were given on mousedown. 7736 else if (typeof beforeDrag === "object") { 7737 _jp.extend(beforeDrag, payload || {}); 7738 } 7739 else { 7740 // or if no beforeDrag data, maybe use the payload on its own. 7741 beforeDrag = payload || {}; 7742 } 7743 7744 if (_continue === false) { 7745 // this is for mootools and yui. returning false from this causes jquery to stop drag. 7746 // the events are wrapped in both mootools and yui anyway, but i don't think returning 7747 // false from the start callback would stop a drag. 7748 if (_jsPlumb.stopDrag) { 7749 _jsPlumb.stopDrag(this.canvas); 7750 } 7751 _dragHandler.stopDrag(); 7752 return false; 7753 } 7754 7755 // --------------------------------------------------------------------------------------------------------------------- 7756 7757 // ok to proceed. 7758 7759 // clear hover for all connections for this endpoint before continuing. 7760 for (var i = 0; i < this.connections.length; i++) { 7761 this.connections[i].setHover(false); 7762 } 7763 7764 this.addClass("endpointDrag"); 7765 _jsPlumb.setConnectionBeingDragged(true); 7766 7767 // if we're not full but there was a connection, make it null. we'll create a new one. 7768 if (jpc && !this.isFull() && this.isSource) { 7769 jpc = null; 7770 } 7771 7772 _jsPlumb.updateOffset({ elId: this.elementId }); 7773 7774 // ---------------- make the element we will drag around, and position it ----------------------------- 7775 7776 var ipco = this._jsPlumb.instance.getOffset(this.canvas), 7777 canvasElement = this.canvas, 7778 ips = this._jsPlumb.instance.getSize(this.canvas); 7779 7780 _makeDraggablePlaceholder(placeholderInfo, _jsPlumb, ipco, ips); 7781 7782 // store the id of the dragging div and the source element. the drop function will pick these up. 7783 _jsPlumb.setAttributes(this.canvas, { 7784 "dragId": placeholderInfo.id, 7785 "elId": this.elementId 7786 }); 7787 7788 // ------------------- create an endpoint that will be our floating endpoint ------------------------------------ 7789 7790 var endpointToFloat = this.dragProxy || this.endpoint; 7791 if (this.dragProxy == null && this.connectionType != null) { 7792 var aae = this._jsPlumb.instance.deriveEndpointAndAnchorSpec(this.connectionType); 7793 if (aae.endpoints[1]) { 7794 endpointToFloat = aae.endpoints[1]; 7795 } 7796 } 7797 var centerAnchor = this._jsPlumb.instance.makeAnchor("Center"); 7798 centerAnchor.isFloating = true; 7799 this._jsPlumb.floatingEndpoint = _makeFloatingEndpoint(this.getPaintStyle(), centerAnchor, endpointToFloat, this.canvas, placeholderInfo.element, _jsPlumb, _newEndpoint, this.scope); 7800 var _savedAnchor = this._jsPlumb.floatingEndpoint.anchor; 7801 7802 7803 if (jpc == null) { 7804 7805 this.setHover(false, false); 7806 // create a connection. one end is this endpoint, the other is a floating endpoint. 7807 jpc = _newConnection({ 7808 sourceEndpoint: this, 7809 targetEndpoint: this._jsPlumb.floatingEndpoint, 7810 source: this.element, // for makeSource with parent option. ensure source element is represented correctly. 7811 target: placeholderInfo.element, 7812 anchors: [ this.anchor, this._jsPlumb.floatingEndpoint.anchor ], 7813 paintStyle: params.connectorStyle, // this can be null. Connection will use the default. 7814 hoverPaintStyle: params.connectorHoverStyle, 7815 connector: params.connector, // this can also be null. Connection will use the default. 7816 overlays: params.connectorOverlays, 7817 type: this.connectionType, 7818 cssClass: this.connectorClass, 7819 hoverClass: this.connectorHoverClass, 7820 scope:params.scope, 7821 data:beforeDrag 7822 }); 7823 jpc.pending = true; 7824 jpc.addClass(_jsPlumb.draggingClass); 7825 this._jsPlumb.floatingEndpoint.addClass(_jsPlumb.draggingClass); 7826 this._jsPlumb.floatingEndpoint.anchor = _savedAnchor; 7827 // fire an event that informs that a connection is being dragged 7828 _jsPlumb.fire("connectionDrag", jpc); 7829 7830 // register the new connection on the drag manager. This connection, at this point, is 'pending', 7831 // and has as its target a temporary element (the 'placeholder'). If the connection subsequently 7832 // becomes established, the anchor manager is informed that the target of the connection has 7833 // changed. 7834 7835 _jsPlumb.anchorManager.newConnection(jpc); 7836 7837 } else { 7838 existingJpc = true; 7839 jpc.setHover(false); 7840 // new anchor idx 7841 var anchorIdx = jpc.endpoints[0].id === this.id ? 0 : 1; 7842 this.detachFromConnection(jpc, null, true); // detach from the connection while dragging is occurring. but dont cleanup automatically. 7843 7844 // store the original scope (issue 57) 7845 var dragScope = _jsPlumb.getDragScope(canvasElement); 7846 _jsPlumb.setAttribute(this.canvas, "originalScope", dragScope); 7847 7848 // fire an event that informs that a connection is being dragged. we do this before 7849 // replacing the original target with the floating element info. 7850 _jsPlumb.fire("connectionDrag", jpc); 7851 7852 // now we replace ourselves with the temporary div we created above: 7853 if (anchorIdx === 0) { 7854 existingJpcParams = [ jpc.source, jpc.sourceId, canvasElement, dragScope ]; 7855 _jsPlumb.anchorManager.sourceChanged(jpc.endpoints[anchorIdx].elementId, placeholderInfo.id, jpc, placeholderInfo.element); 7856 7857 } else { 7858 existingJpcParams = [ jpc.target, jpc.targetId, canvasElement, dragScope ]; 7859 jpc.target = placeholderInfo.element; 7860 jpc.targetId = placeholderInfo.id; 7861 7862 _jsPlumb.anchorManager.updateOtherEndpoint(jpc.sourceId, jpc.endpoints[anchorIdx].elementId, jpc.targetId, jpc); 7863 } 7864 7865 // store the original endpoint and assign the new floating endpoint for the drag. 7866 jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; 7867 7868 // PROVIDE THE SUSPENDED ELEMENT, BE IT A SOURCE OR TARGET (ISSUE 39) 7869 jpc.suspendedElement = jpc.endpoints[anchorIdx].getElement(); 7870 jpc.suspendedElementId = jpc.endpoints[anchorIdx].elementId; 7871 jpc.suspendedElementType = anchorIdx === 0 ? "source" : "target"; 7872 7873 jpc.suspendedEndpoint.setHover(false); 7874 this._jsPlumb.floatingEndpoint.referenceEndpoint = jpc.suspendedEndpoint; 7875 jpc.endpoints[anchorIdx] = this._jsPlumb.floatingEndpoint; 7876 7877 jpc.addClass(_jsPlumb.draggingClass); 7878 this._jsPlumb.floatingEndpoint.addClass(_jsPlumb.draggingClass); 7879 } 7880 7881 // register it and register connection on it. 7882 _jsPlumb.floatingConnections[placeholderInfo.id] = jpc; 7883 // only register for the target endpoint; we will not be dragging the source at any time 7884 // before this connection is either discarded or made into a permanent connection. 7885 _ju.addToList(params.endpointsByElement, placeholderInfo.id, this._jsPlumb.floatingEndpoint); 7886 // tell jsplumb about it 7887 _jsPlumb.currentlyDragging = true; 7888 }.bind(this); 7889 7890 var stop = function () { 7891 _jsPlumb.setConnectionBeingDragged(false); 7892 7893 if (jpc && jpc.endpoints != null) { 7894 // get the actual drop event (decode from library args to stop function) 7895 var originalEvent = _jsPlumb.getDropEvent(arguments); 7896 // unlock the other endpoint (if it is dynamic, it would have been locked at drag start) 7897 var idx = _jsPlumb.getFloatingAnchorIndex(jpc); 7898 jpc.endpoints[idx === 0 ? 1 : 0].anchor.locked = false; 7899 // TODO: Dont want to know about css classes inside jsplumb, ideally. 7900 jpc.removeClass(_jsPlumb.draggingClass); 7901 7902 // if we have the floating endpoint then the connection has not been dropped 7903 // on another endpoint. If it is a new connection we throw it away. If it is an 7904 // existing connection we check to see if we should reattach it, throwing it away 7905 // if not. 7906 if (this._jsPlumb && (jpc.deleteConnectionNow || jpc.endpoints[idx] === this._jsPlumb.floatingEndpoint)) { 7907 // 6a. if the connection was an existing one... 7908 if (existingJpc && jpc.suspendedEndpoint) { 7909 // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the 7910 // floating endpoint has been replaced. 7911 if (idx === 0) { 7912 jpc.floatingElement = jpc.source; 7913 jpc.floatingId = jpc.sourceId; 7914 jpc.floatingEndpoint = jpc.endpoints[0]; 7915 jpc.floatingIndex = 0; 7916 jpc.source = existingJpcParams[0]; 7917 jpc.sourceId = existingJpcParams[1]; 7918 } else { 7919 // keep a copy of the floating element; the anchor manager will want to clean up. 7920 jpc.floatingElement = jpc.target; 7921 jpc.floatingId = jpc.targetId; 7922 jpc.floatingEndpoint = jpc.endpoints[1]; 7923 jpc.floatingIndex = 1; 7924 jpc.target = existingJpcParams[0]; 7925 jpc.targetId = existingJpcParams[1]; 7926 } 7927 7928 var fe = this._jsPlumb.floatingEndpoint; // store for later removal. 7929 // restore the original scope (issue 57) 7930 _jsPlumb.setDragScope(existingJpcParams[2], existingJpcParams[3]); 7931 jpc.endpoints[idx] = jpc.suspendedEndpoint; 7932 // IF the connection should be reattached, or the other endpoint refuses detach, then 7933 // reset the connection to its original state 7934 //if (jpc.isReattach() || jpc._forceReattach || jpc._forceDetach || !jpc.endpoints[idx === 0 ? 1 : 0].detach({connection:jpc, ignoreTarget:false, forceDetach:false, fireEvent:true, originalEvent:originalEvent, endpointBeingDeleted:true})) { 7935 if (jpc.isReattach() || jpc._forceReattach || jpc._forceDetach || !_jsPlumb.deleteConnection(jpc)) { 7936 7937 jpc.setHover(false); 7938 jpc._forceDetach = null; 7939 jpc._forceReattach = null; 7940 this._jsPlumb.floatingEndpoint.detachFromConnection(jpc); 7941 jpc.suspendedEndpoint.addConnection(jpc); 7942 7943 // TODO this code is duplicated in lots of places...and there is nothing external 7944 // in the code; it all refers to the connection itself. we could add a 7945 // `checkSanity(connection)` method to anchorManager that did this. 7946 if (idx === 1) { 7947 _jsPlumb.anchorManager.updateOtherEndpoint(jpc.sourceId, jpc.floatingId, jpc.targetId, jpc); 7948 } 7949 else { 7950 _jsPlumb.anchorManager.sourceChanged(jpc.floatingId, jpc.sourceId, jpc, jpc.source); 7951 } 7952 7953 _jsPlumb.repaint(existingJpcParams[1]); 7954 } 7955 else { 7956 _jsPlumb.deleteObject({endpoint: fe}); 7957 } 7958 } 7959 } 7960 7961 // makeTargets sets this flag, to tell us we have been replaced and should delete this object. 7962 if (this.deleteAfterDragStop) { 7963 _jsPlumb.deleteObject({endpoint: this}); 7964 } 7965 else { 7966 if (this._jsPlumb) { 7967 this.paint({recalc: false}); 7968 } 7969 } 7970 7971 // although the connection is no longer valid, there are use cases where this is useful. 7972 _jsPlumb.fire("connectionDragStop", jpc, originalEvent); 7973 // fire this event to give people more fine-grained control (connectionDragStop fires a lot) 7974 if (jpc.pending) { 7975 _jsPlumb.fire("connectionAborted", jpc, originalEvent); 7976 } 7977 // tell jsplumb that dragging is finished. 7978 _jsPlumb.currentlyDragging = false; 7979 jpc.suspendedElement = null; 7980 jpc.suspendedEndpoint = null; 7981 jpc = null; 7982 } 7983 7984 // if no endpoints, jpc already cleaned up. but still we want to ensure we're reset properly. 7985 // remove the element associated with the floating endpoint 7986 // (and its associated floating endpoint and visual artefacts) 7987 if (placeholderInfo && placeholderInfo.element) { 7988 _jsPlumb.remove(placeholderInfo.element, false, false); 7989 } 7990 // remove the inplace copy 7991 if (inPlaceCopy) { 7992 _jsPlumb.deleteObject({endpoint: inPlaceCopy}); 7993 } 7994 7995 if (this._jsPlumb) { 7996 // make our canvas visible (TODO: hand off to library; we should not know about DOM) 7997 this.canvas.style.visibility = "visible"; 7998 // unlock our anchor 7999 this.anchor.locked = false; 8000 // clear floating anchor. 8001 this._jsPlumb.floatingEndpoint = null; 8002 } 8003 8004 }.bind(this); 8005 8006 dragOptions = _jp.extend(defaultOpts, dragOptions); 8007 dragOptions.scope = this.scope || dragOptions.scope; 8008 dragOptions[beforeStartEvent] = _ju.wrap(dragOptions[beforeStartEvent], beforeStart, false); 8009 dragOptions[startEvent] = _ju.wrap(dragOptions[startEvent], start, false); 8010 // extracted drag handler function so can be used by makeSource 8011 dragOptions[dragEvent] = _ju.wrap(dragOptions[dragEvent], _dragHandler.drag); 8012 dragOptions[stopEvent] = _ju.wrap(dragOptions[stopEvent], stop); 8013 dragOptions.multipleDrop = false; 8014 8015 dragOptions.canDrag = function () { 8016 return this.isSource || this.isTemporarySource || /*(this.isTarget && */this.connections.length > 0/*)*/; 8017 }.bind(this); 8018 8019 _jsPlumb.initDraggable(this.canvas, dragOptions, "internal"); 8020 8021 this.canvas._jsPlumbRelatedElement = this.element; 8022 8023 draggingInitialised = true; 8024 } 8025 }; 8026 8027 var ep = params.endpoint || this._jsPlumb.instance.Defaults.Endpoint || _jp.Defaults.Endpoint; 8028 this.setEndpoint(ep, true); 8029 var anchorParamsToUse = params.anchor ? params.anchor : params.anchors ? params.anchors : (_jsPlumb.Defaults.Anchor || "Top"); 8030 this.setAnchor(anchorParamsToUse, true); 8031 8032 // finally, set type if it was provided 8033 var type = [ "default", (params.type || "")].join(" "); 8034 this.addType(type, params.data, true); 8035 this.canvas = this.endpoint.canvas; 8036 this.canvas._jsPlumb = this; 8037 8038 this.initDraggable(); 8039 8040 // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections 8041 // back onto the endpoint you detached it from. 8042 var _initDropTarget = function (canvas, isTransient, endpoint, referenceEndpoint) { 8043 8044 if (_jp.isDropSupported(this.element)) { 8045 var dropOptions = params.dropOptions || _jsPlumb.Defaults.DropOptions || _jp.Defaults.DropOptions; 8046 dropOptions = _jp.extend({}, dropOptions); 8047 dropOptions.scope = dropOptions.scope || this.scope; 8048 var dropEvent = _jp.dragEvents.drop, 8049 overEvent = _jp.dragEvents.over, 8050 outEvent = _jp.dragEvents.out, 8051 _ep = this, 8052 drop = _jsPlumb.EndpointDropHandler({ 8053 getEndpoint: function () { 8054 return _ep; 8055 }, 8056 jsPlumb: _jsPlumb, 8057 enabled: function () { 8058 return endpoint != null ? endpoint.isEnabled() : true; 8059 }, 8060 isFull: function () { 8061 return endpoint.isFull(); 8062 }, 8063 element: this.element, 8064 elementId: this.elementId, 8065 isSource: this.isSource, 8066 isTarget: this.isTarget, 8067 addClass: function (clazz) { 8068 _ep.addClass(clazz); 8069 }, 8070 removeClass: function (clazz) { 8071 _ep.removeClass(clazz); 8072 }, 8073 isDropAllowed: function () { 8074 return _ep.isDropAllowed.apply(_ep, arguments); 8075 }, 8076 reference:referenceEndpoint, 8077 isRedrop:function(jpc, dhParams) { 8078 return jpc.suspendedEndpoint && dhParams.reference && (jpc.suspendedEndpoint.id === dhParams.reference.id); 8079 } 8080 }); 8081 8082 dropOptions[dropEvent] = _ju.wrap(dropOptions[dropEvent], drop, true); 8083 dropOptions[overEvent] = _ju.wrap(dropOptions[overEvent], function () { 8084 var draggable = _jp.getDragObject(arguments), 8085 id = _jsPlumb.getAttribute(_jp.getElement(draggable), "dragId"), 8086 _jpc = _jsPlumb.floatingConnections[id]; 8087 8088 if (_jpc != null) { 8089 var idx = _jsPlumb.getFloatingAnchorIndex(_jpc); 8090 // here we should fire the 'over' event if we are a target and this is a new connection, 8091 // or we are the same as the floating endpoint. 8092 var _cont = (this.isTarget && idx !== 0) || (_jpc.suspendedEndpoint && this.referenceEndpoint && this.referenceEndpoint.id === _jpc.suspendedEndpoint.id); 8093 if (_cont) { 8094 var bb = _jsPlumb.checkCondition("checkDropAllowed", { 8095 sourceEndpoint: _jpc.endpoints[idx], 8096 targetEndpoint: this, 8097 connection: _jpc 8098 }); 8099 this[(bb ? "add" : "remove") + "Class"](_jsPlumb.endpointDropAllowedClass); 8100 this[(bb ? "remove" : "add") + "Class"](_jsPlumb.endpointDropForbiddenClass); 8101 _jpc.endpoints[idx].anchor.over(this.anchor, this); 8102 } 8103 } 8104 }.bind(this)); 8105 8106 dropOptions[outEvent] = _ju.wrap(dropOptions[outEvent], function () { 8107 var draggable = _jp.getDragObject(arguments), 8108 id = draggable == null ? null : _jsPlumb.getAttribute(_jp.getElement(draggable), "dragId"), 8109 _jpc = id ? _jsPlumb.floatingConnections[id] : null; 8110 8111 if (_jpc != null) { 8112 var idx = _jsPlumb.getFloatingAnchorIndex(_jpc); 8113 var _cont = (this.isTarget && idx !== 0) || (_jpc.suspendedEndpoint && this.referenceEndpoint && this.referenceEndpoint.id === _jpc.suspendedEndpoint.id); 8114 if (_cont) { 8115 this.removeClass(_jsPlumb.endpointDropAllowedClass); 8116 this.removeClass(_jsPlumb.endpointDropForbiddenClass); 8117 _jpc.endpoints[idx].anchor.out(); 8118 } 8119 } 8120 }.bind(this)); 8121 8122 _jsPlumb.initDroppable(canvas, dropOptions, "internal", isTransient); 8123 } 8124 }.bind(this); 8125 8126 // Initialise the endpoint's canvas as a drop target. The drop handler will take care of the logic of whether 8127 // something can actually be dropped. 8128 if (!this.anchor.isFloating) { 8129 _initDropTarget(this.canvas, !(params._transient || this.anchor.isFloating), this, params.reference); 8130 } 8131 8132 return this; 8133 }; 8134 8135 _ju.extend(_jp.Endpoint, _jp.OverlayCapableJsPlumbUIComponent, { 8136 8137 setVisible: function (v, doNotChangeConnections, doNotNotifyOtherEndpoint) { 8138 this._jsPlumb.visible = v; 8139 if (this.canvas) { 8140 this.canvas.style.display = v ? "block" : "none"; 8141 } 8142 this[v ? "showOverlays" : "hideOverlays"](); 8143 if (!doNotChangeConnections) { 8144 for (var i = 0; i < this.connections.length; i++) { 8145 this.connections[i].setVisible(v); 8146 if (!doNotNotifyOtherEndpoint) { 8147 var oIdx = this === this.connections[i].endpoints[0] ? 1 : 0; 8148 // only change the other endpoint if this is its only connection. 8149 if (this.connections[i].endpoints[oIdx].connections.length === 1) { 8150 this.connections[i].endpoints[oIdx].setVisible(v, true, true); 8151 } 8152 } 8153 } 8154 } 8155 }, 8156 getAttachedElements: function () { 8157 return this.connections; 8158 }, 8159 applyType: function (t, doNotRepaint) { 8160 this.setPaintStyle(t.endpointStyle || t.paintStyle, doNotRepaint); 8161 this.setHoverPaintStyle(t.endpointHoverStyle || t.hoverPaintStyle, doNotRepaint); 8162 if (t.maxConnections != null) { 8163 this._jsPlumb.maxConnections = t.maxConnections; 8164 } 8165 if (t.scope) { 8166 this.scope = t.scope; 8167 } 8168 _jp.extend(this, t, typeParameters); 8169 if (t.cssClass != null && this.canvas) { 8170 this._jsPlumb.instance.addClass(this.canvas, t.cssClass); 8171 } 8172 _jp.OverlayCapableJsPlumbUIComponent.applyType(this, t); 8173 }, 8174 isEnabled: function () { 8175 return this._jsPlumb.enabled; 8176 }, 8177 setEnabled: function (e) { 8178 this._jsPlumb.enabled = e; 8179 }, 8180 cleanup: function () { 8181 var anchorClass = this._jsPlumb.instance.endpointAnchorClassPrefix + (this._jsPlumb.currentAnchorClass ? "-" + this._jsPlumb.currentAnchorClass : ""); 8182 _jp.removeClass(this.element, anchorClass); 8183 this.anchor = null; 8184 this.endpoint.cleanup(true); 8185 this.endpoint.destroy(); 8186 this.endpoint = null; 8187 // drag/drop 8188 this._jsPlumb.instance.destroyDraggable(this.canvas, "internal"); 8189 this._jsPlumb.instance.destroyDroppable(this.canvas, "internal"); 8190 }, 8191 setHover: function (h) { 8192 if (this.endpoint && this._jsPlumb && !this._jsPlumb.instance.isConnectionBeingDragged()) { 8193 this.endpoint.setHover(h); 8194 } 8195 }, 8196 isFull: function () { 8197 return this._jsPlumb.maxConnections === 0 ? true : !(this.isFloating() || this._jsPlumb.maxConnections < 0 || this.connections.length < this._jsPlumb.maxConnections); 8198 }, 8199 /** 8200 * private but needs to be exposed. 8201 */ 8202 isFloating: function () { 8203 return this.anchor != null && this.anchor.isFloating; 8204 }, 8205 isConnectedTo: function (endpoint) { 8206 var found = false; 8207 if (endpoint) { 8208 for (var i = 0; i < this.connections.length; i++) { 8209 if (this.connections[i].endpoints[1] === endpoint || this.connections[i].endpoints[0] === endpoint) { 8210 found = true; 8211 break; 8212 } 8213 } 8214 } 8215 return found; 8216 }, 8217 getConnectionCost: function () { 8218 return this._jsPlumb.connectionCost; 8219 }, 8220 setConnectionCost: function (c) { 8221 this._jsPlumb.connectionCost = c; 8222 }, 8223 areConnectionsDirected: function () { 8224 return this._jsPlumb.connectionsDirected; 8225 }, 8226 setConnectionsDirected: function (b) { 8227 this._jsPlumb.connectionsDirected = b; 8228 }, 8229 setElementId: function (_elId) { 8230 this.elementId = _elId; 8231 this.anchor.elementId = _elId; 8232 }, 8233 setReferenceElement: function (_el) { 8234 this.element = _jp.getElement(_el); 8235 }, 8236 setDragAllowedWhenFull: function (allowed) { 8237 this.dragAllowedWhenFull = allowed; 8238 }, 8239 equals: function (endpoint) { 8240 return this.anchor.equals(endpoint.anchor); 8241 }, 8242 getUuid: function () { 8243 return this._jsPlumb.uuid; 8244 }, 8245 computeAnchor: function (params) { 8246 return this.anchor.compute(params); 8247 } 8248 }); 8249 8250 root.jsPlumbInstance.prototype.EndpointDropHandler = function (dhParams) { 8251 return function (e) { 8252 8253 var _jsPlumb = dhParams.jsPlumb; 8254 8255 // remove the classes that are added dynamically. drop is neither forbidden nor allowed now that 8256 // the drop is finishing. 8257 dhParams.removeClass(_jsPlumb.endpointDropAllowedClass); 8258 dhParams.removeClass(_jsPlumb.endpointDropForbiddenClass); 8259 8260 var originalEvent = _jsPlumb.getDropEvent(arguments), 8261 draggable = _jsPlumb.getDragObject(arguments), 8262 id = _jsPlumb.getAttribute(draggable, "dragId"), 8263 elId = _jsPlumb.getAttribute(draggable, "elId"), 8264 scope = _jsPlumb.getAttribute(draggable, "originalScope"), 8265 jpc = _jsPlumb.floatingConnections[id]; 8266 8267 // if no active connection, bail. 8268 if (jpc == null) { 8269 return; 8270 } 8271 8272 // calculate if this is an existing connection. 8273 var existingConnection = jpc.suspendedEndpoint != null; 8274 8275 // if suspended endpoint exists but has been cleaned up, bail. This means it's an existing connection 8276 // that has been detached and will shortly be discarded. 8277 if (existingConnection && jpc.suspendedEndpoint._jsPlumb == null) { 8278 return; 8279 } 8280 8281 // get the drop endpoint. for a normal connection this is just the one that would replace the currently 8282 // floating endpoint. for a makeTarget this is a new endpoint that is created on drop. But we leave that to 8283 // the handler to figure out. 8284 var _ep = dhParams.getEndpoint(jpc); 8285 8286 // If we're not given an endpoint to use, bail. 8287 if (_ep == null) { 8288 return; 8289 } 8290 8291 // if this is a drop back where the connection came from, mark it force reattach and 8292 // return; the stop handler will reattach. without firing an event. 8293 if (dhParams.isRedrop(jpc, dhParams)) { 8294 jpc._forceReattach = true; 8295 jpc.setHover(false); 8296 if (dhParams.maybeCleanup) { 8297 dhParams.maybeCleanup(_ep); 8298 } 8299 return; 8300 } 8301 8302 // ensure we dont bother trying to drop sources on non-source eps, and same for target. 8303 var idx = _jsPlumb.getFloatingAnchorIndex(jpc); 8304 if ((idx === 0 && !dhParams.isSource)|| (idx === 1 && !dhParams.isTarget)){ 8305 if (dhParams.maybeCleanup) { 8306 dhParams.maybeCleanup(_ep); 8307 } 8308 return; 8309 } 8310 8311 if (dhParams.onDrop) { 8312 dhParams.onDrop(jpc); 8313 } 8314 8315 // restore the original scope if necessary (issue 57) 8316 if (scope) { 8317 _jsPlumb.setDragScope(draggable, scope); 8318 } 8319 8320 // if the target of the drop is full, fire an event (we abort below) 8321 // makeTarget: keep. 8322 var isFull = dhParams.isFull(e); 8323 if (isFull) { 8324 _ep.fire("maxConnections", { 8325 endpoint: this, 8326 connection: jpc, 8327 maxConnections: _ep._jsPlumb.maxConnections 8328 }, originalEvent); 8329 } 8330 // 8331 // if endpoint enabled, not full, and matches the index of the floating endpoint... 8332 if (!isFull && dhParams.enabled()) { 8333 var _doContinue = true; 8334 8335 // before testing for beforeDrop, reset the connection's source/target to be the actual DOM elements 8336 // involved (that is, stash any temporary stuff used for dragging. but we need to keep it around in 8337 // order that the anchor manager can clean things up properly). 8338 if (idx === 0) { 8339 jpc.floatingElement = jpc.source; 8340 jpc.floatingId = jpc.sourceId; 8341 jpc.floatingEndpoint = jpc.endpoints[0]; 8342 jpc.floatingIndex = 0; 8343 jpc.source = dhParams.element; 8344 jpc.sourceId = dhParams.elementId; 8345 } else { 8346 jpc.floatingElement = jpc.target; 8347 jpc.floatingId = jpc.targetId; 8348 jpc.floatingEndpoint = jpc.endpoints[1]; 8349 jpc.floatingIndex = 1; 8350 jpc.target = dhParams.element; 8351 jpc.targetId = dhParams.elementId; 8352 } 8353 8354 // if this is an existing connection and detach is not allowed we won't continue. The connection's 8355 // endpoints have been reinstated; everything is back to how it was. 8356 if (existingConnection && jpc.suspendedEndpoint.id !== _ep.id) { 8357 if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_jsPlumb.checkCondition("beforeDetach", jpc)) { 8358 _doContinue = false; 8359 } 8360 } 8361 8362 // ------------ wrap the execution path in a function so we can support asynchronous beforeDrop 8363 8364 var continueFunction = function (optionalData) { 8365 // remove this jpc from the current endpoint, which is a floating endpoint that we will 8366 // subsequently discard. 8367 jpc.endpoints[idx].detachFromConnection(jpc); 8368 8369 // if there's a suspended endpoint, detach it from the connection. 8370 if (jpc.suspendedEndpoint) { 8371 jpc.suspendedEndpoint.detachFromConnection(jpc); 8372 } 8373 8374 jpc.endpoints[idx] = _ep; 8375 _ep.addConnection(jpc); 8376 8377 // copy our parameters in to the connection: 8378 var params = _ep.getParameters(); 8379 for (var aParam in params) { 8380 jpc.setParameter(aParam, params[aParam]); 8381 } 8382 8383 if (!existingConnection) { 8384 // if not an existing connection and 8385 if (params.draggable) { 8386 _jsPlumb.initDraggable(this.element, dhParams.dragOptions, "internal", _jsPlumb); 8387 } 8388 } 8389 else { 8390 var suspendedElementId = jpc.suspendedEndpoint.elementId; 8391 _jsPlumb.fireMoveEvent({ 8392 index: idx, 8393 originalSourceId: idx === 0 ? suspendedElementId : jpc.sourceId, 8394 newSourceId: idx === 0 ? _ep.elementId : jpc.sourceId, 8395 originalTargetId: idx === 1 ? suspendedElementId : jpc.targetId, 8396 newTargetId: idx === 1 ? _ep.elementId : jpc.targetId, 8397 originalSourceEndpoint: idx === 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], 8398 newSourceEndpoint: idx === 0 ? _ep : jpc.endpoints[0], 8399 originalTargetEndpoint: idx === 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], 8400 newTargetEndpoint: idx === 1 ? _ep : jpc.endpoints[1], 8401 connection: jpc 8402 }, originalEvent); 8403 } 8404 8405 if (idx === 1) { 8406 _jsPlumb.anchorManager.updateOtherEndpoint(jpc.sourceId, jpc.floatingId, jpc.targetId, jpc); 8407 } 8408 else { 8409 _jsPlumb.anchorManager.sourceChanged(jpc.floatingId, jpc.sourceId, jpc, jpc.source); 8410 } 8411 8412 // when makeSource has uniqueEndpoint:true, we want to create connections with new endpoints 8413 // that are subsequently deleted. So makeSource sets `finalEndpoint`, which is the Endpoint to 8414 // which the connection should be attached. The `detachFromConnection` call below results in the 8415 // temporary endpoint being cleaned up. 8416 if (jpc.endpoints[0].finalEndpoint) { 8417 var _toDelete = jpc.endpoints[0]; 8418 _toDelete.detachFromConnection(jpc); 8419 jpc.endpoints[0] = jpc.endpoints[0].finalEndpoint; 8420 jpc.endpoints[0].addConnection(jpc); 8421 } 8422 8423 // if optionalData was given, merge it onto the connection's data. 8424 if (_ju.isObject(optionalData)) { 8425 jpc.mergeData(optionalData); 8426 } 8427 // finalise will inform the anchor manager and also add to 8428 // connectionsByScope if necessary. 8429 _jsPlumb.finaliseConnection(jpc, null, originalEvent, false); 8430 jpc.setHover(false); 8431 8432 }.bind(this); 8433 8434 var dontContinueFunction = function () { 8435 // otherwise just put it back on the endpoint it was on before the drag. 8436 if (jpc.suspendedEndpoint) { 8437 jpc.endpoints[idx] = jpc.suspendedEndpoint; 8438 jpc.setHover(false); 8439 jpc._forceDetach = true; 8440 if (idx === 0) { 8441 jpc.source = jpc.suspendedEndpoint.element; 8442 jpc.sourceId = jpc.suspendedEndpoint.elementId; 8443 } else { 8444 jpc.target = jpc.suspendedEndpoint.element; 8445 jpc.targetId = jpc.suspendedEndpoint.elementId; 8446 } 8447 jpc.suspendedEndpoint.addConnection(jpc); 8448 8449 // TODO checkSanity 8450 if (idx === 1) { 8451 _jsPlumb.anchorManager.updateOtherEndpoint(jpc.sourceId, jpc.floatingId, jpc.targetId, jpc); 8452 } 8453 else { 8454 _jsPlumb.anchorManager.sourceChanged(jpc.floatingId, jpc.sourceId, jpc, jpc.source); 8455 } 8456 8457 _jsPlumb.repaint(jpc.sourceId); 8458 jpc._forceDetach = false; 8459 } 8460 }; 8461 8462 // -------------------------------------- 8463 // now check beforeDrop. this will be available only on Endpoints that are setup to 8464 // have a beforeDrop condition (although, secretly, under the hood all Endpoints and 8465 // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because 8466 // it only makes sense to have it on a target endpoint. 8467 _doContinue = _doContinue && dhParams.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope, jpc, _ep);// && jpc.pending; 8468 8469 if (_doContinue) { 8470 continueFunction(_doContinue); 8471 return true; 8472 } 8473 else { 8474 dontContinueFunction(); 8475 } 8476 } 8477 8478 if (dhParams.maybeCleanup) { 8479 dhParams.maybeCleanup(_ep); 8480 } 8481 8482 _jsPlumb.currentlyDragging = false; 8483 }; 8484 }; 8485 }).call(typeof window !== 'undefined' ? window : this); 8486 8487 /* 8488 * jsPlumb Community Edition 8489 * 8490 * Provides a way to visually connect elements on an HTML page, using SVG. 8491 * 8492 * This file contains the code for Connections. 8493 * 8494 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 8495 * 8496 * https://jsplumbtoolkit.com 8497 * https://github.com/jsplumb/jsplumb 8498 * 8499 * Dual licensed under the MIT and GPL2 licenses. 8500 */ 8501 ; 8502 (function () { 8503 8504 "use strict"; 8505 var root = this, 8506 _jp = root.jsPlumb, 8507 _ju = root.jsPlumbUtil; 8508 8509 var makeConnector = function (_jsPlumb, renderMode, connectorName, connectorArgs, forComponent) { 8510 if (!_jsPlumb.Defaults.DoNotThrowErrors && _jp.Connectors[renderMode][connectorName] == null) { 8511 throw { msg: "jsPlumb: unknown connector type '" + connectorName + "'" }; 8512 } 8513 8514 return new _jp.Connectors[renderMode][connectorName](connectorArgs, forComponent); 8515 }, 8516 _makeAnchor = function (anchorParams, elementId, _jsPlumb) { 8517 return (anchorParams) ? _jsPlumb.makeAnchor(anchorParams, elementId, _jsPlumb) : null; 8518 }, 8519 _updateConnectedClass = function (conn, element, _jsPlumb, remove) { 8520 if (element != null) { 8521 element._jsPlumbConnections = element._jsPlumbConnections || {}; 8522 if (remove) { 8523 delete element._jsPlumbConnections[conn.id]; 8524 } 8525 else { 8526 element._jsPlumbConnections[conn.id] = true; 8527 } 8528 8529 if (_ju.isEmpty(element._jsPlumbConnections)) { 8530 _jsPlumb.removeClass(element, _jsPlumb.connectedClass); 8531 } 8532 else { 8533 _jsPlumb.addClass(element, _jsPlumb.connectedClass); 8534 } 8535 } 8536 }; 8537 8538 _jp.Connection = function (params) { 8539 var _newEndpoint = params.newEndpoint; 8540 8541 this.id = params.id; 8542 this.connector = null; 8543 this.idPrefix = "_jsplumb_c_"; 8544 this.defaultLabelLocation = 0.5; 8545 this.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; 8546 // if a new connection is the result of moving some existing connection, params.previousConnection 8547 // will have that Connection in it. listeners for the jsPlumbConnection event can look for that 8548 // member and take action if they need to. 8549 this.previousConnection = params.previousConnection; 8550 this.source = _jp.getElement(params.source); 8551 this.target = _jp.getElement(params.target); 8552 // sourceEndpoint and targetEndpoint override source/target, if they are present. but 8553 // source is not overridden if the Endpoint has declared it is not the final target of a connection; 8554 // instead we use the source that the Endpoint declares will be the final source element. 8555 if (params.sourceEndpoint) { 8556 this.source = params.sourceEndpoint.getElement(); 8557 } 8558 if (params.targetEndpoint) { 8559 this.target = params.targetEndpoint.getElement(); 8560 } 8561 8562 _jp.OverlayCapableJsPlumbUIComponent.apply(this, arguments); 8563 8564 this.sourceId = this._jsPlumb.instance.getId(this.source); 8565 this.targetId = this._jsPlumb.instance.getId(this.target); 8566 this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. 8567 this.endpoints = []; 8568 this.endpointStyles = []; 8569 8570 var _jsPlumb = this._jsPlumb.instance; 8571 8572 _jsPlumb.manage(this.sourceId, this.source); 8573 _jsPlumb.manage(this.targetId, this.target); 8574 8575 this._jsPlumb.visible = true; 8576 this._jsPlumb.editable = params.editable === true; 8577 this._jsPlumb.params = { 8578 cssClass: params.cssClass, 8579 container: params.container, 8580 "pointer-events": params["pointer-events"], 8581 editorParams: params.editorParams, 8582 overlays: params.overlays 8583 }; 8584 this._jsPlumb.lastPaintedAt = null; 8585 8586 // listen to mouseover and mouseout events passed from the container delegate. 8587 this.bind("mouseover", function () { 8588 this.setHover(true); 8589 }.bind(this)); 8590 this.bind("mouseout", function () { 8591 this.setHover(false); 8592 }.bind(this)); 8593 8594 this.editableRequested = params.editable !== false; 8595 this.setEditable = function(e) { 8596 return this.connector ? this.connector.setEditable(e) : false; 8597 }; 8598 this.isEditable = function() { return this.connector ? this.connector.isEditable() : false; }; 8599 this.isEditing = function() { return this.connector ? this.connector.isEditing() : false; }; 8600 8601 // INITIALISATION CODE 8602 8603 this.makeEndpoint = function (isSource, el, elId, ep) { 8604 elId = elId || this._jsPlumb.instance.getId(el); 8605 return this.prepareEndpoint(_jsPlumb, _newEndpoint, this, ep, isSource ? 0 : 1, params, el, elId); 8606 }; 8607 8608 // if type given, get the endpoint definitions mapping to that type from the jsplumb instance, and use those. 8609 // we apply types at the end of this constructor but endpoints are only honoured in a type definition at 8610 // create time. 8611 if (params.type) { 8612 params.endpoints = params.endpoints || this._jsPlumb.instance.deriveEndpointAndAnchorSpec(params.type).endpoints; 8613 } 8614 8615 var eS = this.makeEndpoint(true, this.source, this.sourceId, params.sourceEndpoint), 8616 eT = this.makeEndpoint(false, this.target, this.targetId, params.targetEndpoint); 8617 8618 if (eS) { 8619 _ju.addToList(params.endpointsByElement, this.sourceId, eS); 8620 } 8621 if (eT) { 8622 _ju.addToList(params.endpointsByElement, this.targetId, eT); 8623 } 8624 // if scope not set, set it to be the scope for the source endpoint. 8625 if (!this.scope) { 8626 this.scope = this.endpoints[0].scope; 8627 } 8628 8629 // if explicitly told to (or not to) delete endpoints when empty, override endpoint's preferences 8630 if (params.deleteEndpointsOnEmpty != null) { 8631 this.endpoints[0].setDeleteOnEmpty(params.deleteEndpointsOnEmpty); 8632 this.endpoints[1].setDeleteOnEmpty(params.deleteEndpointsOnEmpty); 8633 } 8634 // else { 8635 // // otherwise, unless the endpoints say otherwise, mark them for deletion. 8636 // if (!this.endpoints[0]._doNotDeleteOnDetach) this.endpoints[0]._deleteOnDetach = true; 8637 // if (!this.endpoints[1]._doNotDeleteOnDetach) this.endpoints[1]._deleteOnDetach = true; 8638 // } 8639 8640 // -------------------------- DEFAULT TYPE --------------------------------------------- 8641 8642 // DETACHABLE 8643 var _detachable = _jsPlumb.Defaults.ConnectionsDetachable; 8644 if (params.detachable === false) { 8645 _detachable = false; 8646 } 8647 if (this.endpoints[0].connectionsDetachable === false) { 8648 _detachable = false; 8649 } 8650 if (this.endpoints[1].connectionsDetachable === false) { 8651 _detachable = false; 8652 } 8653 // REATTACH 8654 var _reattach = params.reattach || this.endpoints[0].reattachConnections || this.endpoints[1].reattachConnections || _jsPlumb.Defaults.ReattachConnections; 8655 8656 this.appendToDefaultType({ 8657 detachable: _detachable, 8658 reattach: _reattach, 8659 paintStyle:this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _jsPlumb.Defaults.PaintStyle || _jp.Defaults.PaintStyle, 8660 hoverPaintStyle:this.endpoints[0].connectorHoverStyle || this.endpoints[1].connectorHoverStyle || params.hoverPaintStyle || _jsPlumb.Defaults.HoverPaintStyle || _jp.Defaults.HoverPaintStyle 8661 }); 8662 8663 var _suspendedAt = _jsPlumb.getSuspendedAt(); 8664 if (!_jsPlumb.isSuspendDrawing()) { 8665 // paint the endpoints 8666 var myInfo = _jsPlumb.getCachedData(this.sourceId), 8667 myOffset = myInfo.o, myWH = myInfo.s, 8668 otherInfo = _jsPlumb.getCachedData(this.targetId), 8669 otherOffset = otherInfo.o, 8670 otherWH = otherInfo.s, 8671 initialTimestamp = _suspendedAt || _jsPlumb.timestamp(), 8672 anchorLoc = this.endpoints[0].anchor.compute({ 8673 xy: [ myOffset.left, myOffset.top ], wh: myWH, element: this.endpoints[0], 8674 elementId: this.endpoints[0].elementId, 8675 txy: [ otherOffset.left, otherOffset.top ], twh: otherWH, tElement: this.endpoints[1], 8676 timestamp: initialTimestamp 8677 }); 8678 8679 this.endpoints[0].paint({ anchorLoc: anchorLoc, timestamp: initialTimestamp }); 8680 8681 anchorLoc = this.endpoints[1].anchor.compute({ 8682 xy: [ otherOffset.left, otherOffset.top ], wh: otherWH, element: this.endpoints[1], 8683 elementId: this.endpoints[1].elementId, 8684 txy: [ myOffset.left, myOffset.top ], twh: myWH, tElement: this.endpoints[0], 8685 timestamp: initialTimestamp 8686 }); 8687 this.endpoints[1].paint({ anchorLoc: anchorLoc, timestamp: initialTimestamp }); 8688 } 8689 8690 this.getTypeDescriptor = function () { 8691 return "connection"; 8692 }; 8693 this.getAttachedElements = function () { 8694 return this.endpoints; 8695 }; 8696 8697 this.isDetachable = function () { 8698 return this._jsPlumb.detachable === true; 8699 }; 8700 this.setDetachable = function (detachable) { 8701 this._jsPlumb.detachable = detachable === true; 8702 }; 8703 this.isReattach = function () { 8704 return this._jsPlumb.reattach === true || this.endpoints[0].reattachConnections === true || this.endpoints[1].reattachConnections === true; 8705 }; 8706 this.setReattach = function (reattach) { 8707 this._jsPlumb.reattach = reattach === true; 8708 }; 8709 8710 // this["delete"] = function() { 8711 // this.endpoints[0].detachFromConnection(this); 8712 // this.endpoints[1].detachFromConnection(this); 8713 // params.deleteConnection(this); 8714 // }; 8715 8716 // END INITIALISATION CODE 8717 8718 8719 // COST + DIRECTIONALITY 8720 // if cost not supplied, try to inherit from source endpoint 8721 this._jsPlumb.cost = params.cost || this.endpoints[0].getConnectionCost(); 8722 this._jsPlumb.directed = params.directed; 8723 // inherit directed flag if set no source endpoint 8724 if (params.directed == null) { 8725 this._jsPlumb.directed = this.endpoints[0].areConnectionsDirected(); 8726 } 8727 // END COST + DIRECTIONALITY 8728 8729 // PARAMETERS 8730 // merge all the parameters objects into the connection. parameters set 8731 // on the connection take precedence; then source endpoint params, then 8732 // finally target endpoint params. 8733 var _p = _jp.extend({}, this.endpoints[1].getParameters()); 8734 _jp.extend(_p, this.endpoints[0].getParameters()); 8735 _jp.extend(_p, this.getParameters()); 8736 this.setParameters(_p); 8737 // END PARAMETERS 8738 8739 // PAINTING 8740 8741 this.setConnector(this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _jsPlumb.Defaults.Connector || _jp.Defaults.Connector, true); 8742 if (params.geometry) { 8743 this.connector.setGeometry(params.geometry); 8744 } 8745 var data = params.data == null || !_ju.isObject(params.data) ? {} : params.data; 8746 this.getData = function() { return data; }; 8747 this.setData = function(d) { data = d || {}; }; 8748 this.mergeData = function(d) { data = _jp.extend(data, d); }; 8749 8750 // the very last thing we do is apply types, if there are any. 8751 var _types = [ "default", this.endpoints[0].connectionType, this.endpoints[1].connectionType, params.type ].join(" "); 8752 if (/[^\s]/.test(_types)) { 8753 this.addType(_types, params.data, true); 8754 } 8755 8756 this.updateConnectedClass(); 8757 8758 // END PAINTING 8759 }; 8760 8761 _ju.extend(_jp.Connection, _jp.OverlayCapableJsPlumbUIComponent, { 8762 applyType: function (t, doNotRepaint, typeMap) { 8763 8764 // none of these things result in the creation of objects so can be ignored. 8765 if (t.detachable != null) { 8766 this.setDetachable(t.detachable); 8767 } 8768 if (t.reattach != null) { 8769 this.setReattach(t.reattach); 8770 } 8771 if (t.scope) { 8772 this.scope = t.scope; 8773 } 8774 8775 if (t.cssClass != null && this.canvas) { 8776 this._jsPlumb.instance.addClass(this.canvas, t.cssClass); 8777 } 8778 8779 var _anchors = null; 8780 // this also results in the creation of objects. 8781 if (t.anchor) { 8782 // note that even if the param was anchor, we store `anchors`. 8783 _anchors = this.getCachedTypeItem("anchors", typeMap.anchor); 8784 if (_anchors == null) { 8785 _anchors = [ this._jsPlumb.instance.makeAnchor(t.anchor), this._jsPlumb.instance.makeAnchor(t.anchor) ]; 8786 this.cacheTypeItem("anchors", _anchors, typeMap.anchor); 8787 } 8788 } 8789 else if (t.anchors) { 8790 _anchors = this.getCachedTypeItem("anchors", typeMap.anchors); 8791 if (_anchors == null) { 8792 _anchors = [ 8793 this._jsPlumb.instance.makeAnchor(t.anchors[0]), 8794 this._jsPlumb.instance.makeAnchor(t.anchors[1]) 8795 ]; 8796 this.cacheTypeItem("anchors", _anchors, typeMap.anchors); 8797 } 8798 } 8799 if (_anchors != null) { 8800 this.endpoints[0].anchor = _anchors[0]; 8801 this.endpoints[1].anchor = _anchors[1]; 8802 if (this.endpoints[1].anchor.isDynamic) { 8803 this._jsPlumb.instance.repaint(this.endpoints[1].elementId); 8804 } 8805 } 8806 8807 _jp.OverlayCapableJsPlumbUIComponent.applyType(this, t); 8808 }, 8809 addClass: function (c, informEndpoints) { 8810 if (informEndpoints) { 8811 this.endpoints[0].addClass(c); 8812 this.endpoints[1].addClass(c); 8813 if (this.suspendedEndpoint) { 8814 this.suspendedEndpoint.addClass(c); 8815 } 8816 } 8817 if (this.connector) { 8818 this.connector.addClass(c); 8819 } 8820 }, 8821 removeClass: function (c, informEndpoints) { 8822 if (informEndpoints) { 8823 this.endpoints[0].removeClass(c); 8824 this.endpoints[1].removeClass(c); 8825 if (this.suspendedEndpoint) { 8826 this.suspendedEndpoint.removeClass(c); 8827 } 8828 } 8829 if (this.connector) { 8830 this.connector.removeClass(c); 8831 } 8832 }, 8833 isVisible: function () { 8834 return this._jsPlumb.visible; 8835 }, 8836 setVisible: function (v) { 8837 this._jsPlumb.visible = v; 8838 if (this.connector) { 8839 this.connector.setVisible(v); 8840 } 8841 this.repaint(); 8842 }, 8843 cleanup: function () { 8844 this.updateConnectedClass(true); 8845 this.endpoints = null; 8846 this.source = null; 8847 this.target = null; 8848 if (this.connector != null) { 8849 this.connector.cleanup(true); 8850 this.connector.destroy(true); 8851 } 8852 this.connector = null; 8853 }, 8854 updateConnectedClass:function(remove) { 8855 if (this._jsPlumb) { 8856 _updateConnectedClass(this, this.source, this._jsPlumb.instance, remove); 8857 _updateConnectedClass(this, this.target, this._jsPlumb.instance, remove); 8858 } 8859 }, 8860 setHover: function (state) { 8861 if (this.connector && this._jsPlumb && !this._jsPlumb.instance.isConnectionBeingDragged()) { 8862 this.connector.setHover(state); 8863 root.jsPlumb[state ? "addClass" : "removeClass"](this.source, this._jsPlumb.instance.hoverSourceClass); 8864 root.jsPlumb[state ? "addClass" : "removeClass"](this.target, this._jsPlumb.instance.hoverTargetClass); 8865 } 8866 }, 8867 getUuids:function() { 8868 return [ this.endpoints[0].getUuid(), this.endpoints[1].getUuid() ]; 8869 }, 8870 getCost: function () { 8871 return this._jsPlumb ? this._jsPlumb.cost : -Infinity; 8872 }, 8873 setCost: function (c) { 8874 this._jsPlumb.cost = c; 8875 }, 8876 isDirected: function () { 8877 return this._jsPlumb.directed === true; 8878 }, 8879 getConnector: function () { 8880 return this.connector; 8881 }, 8882 getGeometry : function() { 8883 return this.connector ? this.connector.getGeometry() : null; 8884 }, 8885 setGeometry : function(g) { 8886 if (this.connector) { 8887 this.connector.setGeometry(g); 8888 } 8889 }, 8890 prepareConnector:function(connectorSpec, typeId) { 8891 var connectorArgs = { 8892 _jsPlumb: this._jsPlumb.instance, 8893 cssClass: (this._jsPlumb.params.cssClass || "") + (this.isEditable() ? this._jsPlumb.instance.editableConnectorClass : ""), 8894 container: this._jsPlumb.params.container, 8895 "pointer-events": this._jsPlumb.params["pointer-events"], 8896 editable:this.editableRequested 8897 }, 8898 renderMode = this._jsPlumb.instance.getRenderMode(), 8899 connector; 8900 8901 if (_ju.isString(connectorSpec)) { 8902 connector = makeConnector(this._jsPlumb.instance, renderMode, connectorSpec, connectorArgs, this); 8903 } // lets you use a string as shorthand. 8904 else if (_ju.isArray(connectorSpec)) { 8905 if (connectorSpec.length === 1) { 8906 connector = makeConnector(this._jsPlumb.instance, renderMode, connectorSpec[0], connectorArgs, this); 8907 } 8908 else { 8909 connector = makeConnector(this._jsPlumb.instance, renderMode, connectorSpec[0], _ju.merge(connectorSpec[1], connectorArgs), this); 8910 } 8911 } 8912 if (typeId != null) { 8913 connector.typeId = typeId; 8914 } 8915 return connector; 8916 }, 8917 setPreparedConnector: function(connector, doNotRepaint, doNotChangeListenerComponent, typeId) { 8918 8919 var previous, previousClasses = ""; 8920 // the connector will not be cleaned up if it was set as part of a type, because `typeId` will be set on it 8921 // and we havent passed in `true` for "force" here. 8922 if (this.connector != null) { 8923 previous = this.connector; 8924 previousClasses = previous.getClass(); 8925 this.connector.cleanup(); 8926 this.connector.destroy(); 8927 } 8928 8929 this.connector = connector; 8930 if (typeId) { 8931 this.cacheTypeItem("connector", connector, typeId); 8932 } 8933 8934 this.canvas = this.connector.canvas; 8935 this.bgCanvas = this.connector.bgCanvas; 8936 8937 // put classes from prior connector onto the canvas 8938 this.addClass(previousClasses); 8939 8940 // new: instead of binding listeners per connector, we now just have one delegate on the container. 8941 // so for that handler we set the connection as the '_jsPlumb' member of the canvas element, and 8942 // bgCanvas, if it exists, which it does right now in the VML renderer, so it won't from v 2.0.0 onwards. 8943 if (this.canvas) { 8944 this.canvas._jsPlumb = this; 8945 } 8946 if (this.bgCanvas) { 8947 this.bgCanvas._jsPlumb = this; 8948 } 8949 8950 if (previous != null) { 8951 var o = this.getOverlays(); 8952 for (var i = 0; i < o.length; i++) { 8953 if (o[i].transfer) { 8954 o[i].transfer(this.connector); 8955 } 8956 } 8957 } 8958 8959 if (!doNotChangeListenerComponent) { 8960 this.setListenerComponent(this.connector); 8961 } 8962 if (!doNotRepaint) { 8963 this.repaint(); 8964 } 8965 }, 8966 setConnector: function (connectorSpec, doNotRepaint, doNotChangeListenerComponent, typeId) { 8967 var connector = this.prepareConnector(connectorSpec, typeId); 8968 this.setPreparedConnector(connector, doNotRepaint, doNotChangeListenerComponent, typeId); 8969 }, 8970 paint: function (params) { 8971 8972 if (!this._jsPlumb.instance.isSuspendDrawing() && this._jsPlumb.visible) { 8973 params = params || {}; 8974 var timestamp = params.timestamp, 8975 // if the moving object is not the source we must transpose the two references. 8976 swap = false, 8977 tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, 8978 tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; 8979 8980 if (timestamp == null || timestamp !== this._jsPlumb.lastPaintedAt) { 8981 var sourceInfo = this._jsPlumb.instance.updateOffset({elId:sId}).o, 8982 targetInfo = this._jsPlumb.instance.updateOffset({elId:tId}).o, 8983 sE = this.endpoints[sIdx], tE = this.endpoints[tIdx]; 8984 8985 var sAnchorP = sE.anchor.getCurrentLocation({xy: [sourceInfo.left, sourceInfo.top], wh: [sourceInfo.width, sourceInfo.height], element: sE, timestamp: timestamp}), 8986 tAnchorP = tE.anchor.getCurrentLocation({xy: [targetInfo.left, targetInfo.top], wh: [targetInfo.width, targetInfo.height], element: tE, timestamp: timestamp}); 8987 8988 this.connector.resetBounds(); 8989 8990 this.connector.compute({ 8991 sourcePos: sAnchorP, 8992 targetPos: tAnchorP, 8993 sourceEndpoint: this.endpoints[sIdx], 8994 targetEndpoint: this.endpoints[tIdx], 8995 "stroke-width": this._jsPlumb.paintStyleInUse.strokeWidth, 8996 sourceInfo: sourceInfo, 8997 targetInfo: targetInfo 8998 }); 8999 9000 var overlayExtents = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity }; 9001 9002 // compute overlays. we do this first so we can get their placements, and adjust the 9003 // container if needs be (if an overlay would be clipped) 9004 for (var i in this._jsPlumb.overlays) { 9005 if (this._jsPlumb.overlays.hasOwnProperty(i)) { 9006 var o = this._jsPlumb.overlays[i]; 9007 if (o.isVisible()) { 9008 this._jsPlumb.overlayPlacements[i] = o.draw(this.connector, this._jsPlumb.paintStyleInUse, this.getAbsoluteOverlayPosition(o)); 9009 overlayExtents.minX = Math.min(overlayExtents.minX, this._jsPlumb.overlayPlacements[i].minX); 9010 overlayExtents.maxX = Math.max(overlayExtents.maxX, this._jsPlumb.overlayPlacements[i].maxX); 9011 overlayExtents.minY = Math.min(overlayExtents.minY, this._jsPlumb.overlayPlacements[i].minY); 9012 overlayExtents.maxY = Math.max(overlayExtents.maxY, this._jsPlumb.overlayPlacements[i].maxY); 9013 } 9014 } 9015 } 9016 9017 var lineWidth = parseFloat(this._jsPlumb.paintStyleInUse.strokeWidth || 1) / 2, 9018 outlineWidth = parseFloat(this._jsPlumb.paintStyleInUse.strokeWidth || 0), 9019 extents = { 9020 xmin: Math.min(this.connector.bounds.minX - (lineWidth + outlineWidth), overlayExtents.minX), 9021 ymin: Math.min(this.connector.bounds.minY - (lineWidth + outlineWidth), overlayExtents.minY), 9022 xmax: Math.max(this.connector.bounds.maxX + (lineWidth + outlineWidth), overlayExtents.maxX), 9023 ymax: Math.max(this.connector.bounds.maxY + (lineWidth + outlineWidth), overlayExtents.maxY) 9024 }; 9025 // paint the connector. 9026 this.connector.paint(this._jsPlumb.paintStyleInUse, null, extents); 9027 // and then the overlays 9028 for (var j in this._jsPlumb.overlays) { 9029 if (this._jsPlumb.overlays.hasOwnProperty(j)) { 9030 var p = this._jsPlumb.overlays[j]; 9031 if (p.isVisible()) { 9032 p.paint(this._jsPlumb.overlayPlacements[j], extents); 9033 } 9034 } 9035 } 9036 } 9037 this._jsPlumb.lastPaintedAt = timestamp; 9038 } 9039 }, 9040 repaint: function (params) { 9041 params = params || {}; 9042 this.paint({ elId: this.sourceId, recalc: !(params.recalc === false), timestamp: params.timestamp}); 9043 }, 9044 prepareEndpoint: function (_jsPlumb, _newEndpoint, conn, existing, index, params, element, elementId) { 9045 var e; 9046 if (existing) { 9047 conn.endpoints[index] = existing; 9048 existing.addConnection(conn); 9049 } else { 9050 if (!params.endpoints) { 9051 params.endpoints = [ null, null ]; 9052 } 9053 var ep = params.endpoints[index] || params.endpoint || _jsPlumb.Defaults.Endpoints[index] || _jp.Defaults.Endpoints[index] || _jsPlumb.Defaults.Endpoint || _jp.Defaults.Endpoint; 9054 if (!params.endpointStyles) { 9055 params.endpointStyles = [ null, null ]; 9056 } 9057 if (!params.endpointHoverStyles) { 9058 params.endpointHoverStyles = [ null, null ]; 9059 } 9060 var es = params.endpointStyles[index] || params.endpointStyle || _jsPlumb.Defaults.EndpointStyles[index] || _jp.Defaults.EndpointStyles[index] || _jsPlumb.Defaults.EndpointStyle || _jp.Defaults.EndpointStyle; 9061 // Endpoints derive their fill from the connector's stroke, if no fill was specified. 9062 if (es.fill == null && params.paintStyle != null) { 9063 es.fill = params.paintStyle.stroke; 9064 } 9065 9066 if (es.outlineStroke == null && params.paintStyle != null) { 9067 es.outlineStroke = params.paintStyle.outlineStroke; 9068 } 9069 if (es.outlineWidth == null && params.paintStyle != null) { 9070 es.outlineWidth = params.paintStyle.outlineWidth; 9071 } 9072 9073 var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _jsPlumb.Defaults.EndpointHoverStyles[index] || _jp.Defaults.EndpointHoverStyles[index] || _jsPlumb.Defaults.EndpointHoverStyle || _jp.Defaults.EndpointHoverStyle; 9074 // endpoint hover fill style is derived from connector's hover stroke style 9075 if (params.hoverPaintStyle != null) { 9076 if (ehs == null) { 9077 ehs = {}; 9078 } 9079 if (ehs.fill == null) { 9080 ehs.fill = params.hoverPaintStyle.stroke; 9081 } 9082 } 9083 var a = params.anchors ? params.anchors[index] : 9084 params.anchor ? params.anchor : 9085 _makeAnchor(_jsPlumb.Defaults.Anchors[index], elementId, _jsPlumb) || 9086 _makeAnchor(_jp.Defaults.Anchors[index], elementId, _jsPlumb) || 9087 _makeAnchor(_jsPlumb.Defaults.Anchor, elementId, _jsPlumb) || 9088 _makeAnchor(_jp.Defaults.Anchor, elementId, _jsPlumb), 9089 u = params.uuids ? params.uuids[index] : null; 9090 9091 e = _newEndpoint({ 9092 paintStyle: es, hoverPaintStyle: ehs, endpoint: ep, connections: [ conn ], 9093 uuid: u, anchor: a, source: element, scope: params.scope, 9094 reattach: params.reattach || _jsPlumb.Defaults.ReattachConnections, 9095 detachable: params.detachable || _jsPlumb.Defaults.ConnectionsDetachable 9096 }); 9097 if (existing == null) { 9098 e.setDeleteOnEmpty(true); 9099 } 9100 conn.endpoints[index] = e; 9101 9102 if (params.drawEndpoints === false) { 9103 e.setVisible(false, true, true); 9104 } 9105 9106 } 9107 return e; 9108 } 9109 9110 }); // END Connection class 9111 }).call(typeof window !== 'undefined' ? window : this); 9112 9113 /* 9114 * jsPlumb Community Edition 9115 * 9116 * Provides a way to visually connect elements on an HTML page, using SVG. 9117 * 9118 * This file contains the code for creating and manipulating anchors. 9119 * 9120 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 9121 * 9122 * https://jsplumbtoolkit.com 9123 * https://github.com/jsplumb/jsplumb 9124 * 9125 * Dual licensed under the MIT and GPL2 licenses. 9126 */ 9127 ; 9128 (function () { 9129 9130 "use strict"; 9131 9132 var root = this, 9133 _ju = root.jsPlumbUtil, 9134 _jp = root.jsPlumb; 9135 9136 // 9137 // manages anchors for all elements. 9138 // 9139 _jp.AnchorManager = function (params) { 9140 var _amEndpoints = {}, 9141 continuousAnchorLocations = {}, 9142 userDefinedContinuousAnchorLocations = {}, 9143 continuousAnchorOrientations = {}, 9144 Orientation = { HORIZONTAL: "horizontal", VERTICAL: "vertical", DIAGONAL: "diagonal", IDENTITY: "identity" }, 9145 axes = ["left", "top", "right", "bottom"], 9146 connectionsByElementId = {}, 9147 self = this, 9148 anchorLists = {}, 9149 jsPlumbInstance = params.jsPlumbInstance, 9150 floatingConnections = {}, 9151 calculateOrientation = function (sourceId, targetId, sd, td, sourceAnchor, targetAnchor) { 9152 9153 if (sourceId === targetId) { 9154 return { 9155 orientation: Orientation.IDENTITY, 9156 a: ["top", "top"] 9157 }; 9158 } 9159 9160 var theta = Math.atan2((td.centery - sd.centery), (td.centerx - sd.centerx)), 9161 theta2 = Math.atan2((sd.centery - td.centery), (sd.centerx - td.centerx)); 9162 9163 // -------------------------------------------------------------------------------------- 9164 9165 // improved face calculation. get midpoints of each face for source and target, then put in an array with all combinations of 9166 // source/target faces. sort this array by distance between midpoints. the entry at index 0 is our preferred option. we can 9167 // go through the array one by one until we find an entry in which each requested face is supported. 9168 var candidates = [], midpoints = { }; 9169 (function (types, dim) { 9170 for (var i = 0; i < types.length; i++) { 9171 midpoints[types[i]] = { 9172 "left": [ dim[i].left, dim[i].centery ], 9173 "right": [ dim[i].right, dim[i].centery ], 9174 "top": [ dim[i].centerx, dim[i].top ], 9175 "bottom": [ dim[i].centerx , dim[i].bottom] 9176 }; 9177 } 9178 })([ "source", "target" ], [ sd, td ]); 9179 9180 for (var sf = 0; sf < axes.length; sf++) { 9181 for (var tf = 0; tf < axes.length; tf++) { 9182 candidates.push({ 9183 source: axes[sf], 9184 target: axes[tf], 9185 dist: Biltong.lineLength(midpoints.source[axes[sf]], midpoints.target[axes[tf]]) 9186 }); 9187 } 9188 } 9189 9190 candidates.sort(function (a, b) { 9191 return a.dist < b.dist ? -1 : a.dist > b.dist ? 1 : 0; 9192 }); 9193 9194 // now go through this list and try to get an entry that satisfies both (there will be one, unless one of the anchors 9195 // declares no available faces) 9196 var sourceEdge = candidates[0].source, targetEdge = candidates[0].target; 9197 for (var i = 0; i < candidates.length; i++) { 9198 9199 if (!sourceAnchor.isContinuous || sourceAnchor.isEdgeSupported(candidates[i].source)) { 9200 sourceEdge = candidates[i].source; 9201 } 9202 else { 9203 sourceEdge = null; 9204 } 9205 9206 if (!targetAnchor.isContinuous || targetAnchor.isEdgeSupported(candidates[i].target)) { 9207 targetEdge = candidates[i].target; 9208 } 9209 else { 9210 targetEdge = null; 9211 } 9212 9213 if (sourceEdge != null && targetEdge != null) { 9214 break; 9215 } 9216 } 9217 9218 // -------------------------------------------------------------------------------------- 9219 9220 return { 9221 a: [ sourceEdge, targetEdge ], 9222 theta: theta, 9223 theta2: theta2 9224 }; 9225 }, 9226 // used by placeAnchors function 9227 placeAnchorsOnLine = function (desc, elementDimensions, elementPosition, connections, horizontal, otherMultiplier, reverse) { 9228 var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); 9229 9230 for (var i = 0; i < connections.length; i++) { 9231 var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; 9232 if (reverse) { 9233 val = elementDimensions[horizontal ? 0 : 1] - val; 9234 } 9235 9236 var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], 9237 dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; 9238 9239 a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); 9240 } 9241 9242 return a; 9243 }, 9244 // used by edgeSortFunctions 9245 currySort = function (reverseAngles) { 9246 return function (a, b) { 9247 var r = true; 9248 if (reverseAngles) { 9249 r = a[0][0] < b[0][0]; 9250 } 9251 else { 9252 r = a[0][0] > b[0][0]; 9253 } 9254 return r === false ? -1 : 1; 9255 }; 9256 }, 9257 // used by edgeSortFunctions 9258 leftSort = function (a, b) { 9259 // first get adjusted values 9260 var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], 9261 p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; 9262 if (p1 > p2) { 9263 return 1; 9264 } 9265 else { 9266 return -1; 9267 } 9268 }, 9269 // used by placeAnchors 9270 edgeSortFunctions = { 9271 "top": function (a, b) { 9272 return a[0] > b[0] ? 1 : -1; 9273 }, 9274 "right": currySort(true), 9275 "bottom": currySort(true), 9276 "left": leftSort 9277 }, 9278 // used by placeAnchors 9279 _sortHelper = function (_array, _fn) { 9280 return _array.sort(_fn); 9281 }, 9282 // used by AnchorManager.redraw 9283 placeAnchors = function (elementId, _anchorLists) { 9284 var cd = jsPlumbInstance.getCachedData(elementId), sS = cd.s, sO = cd.o, 9285 placeSomeAnchors = function (desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) { 9286 if (unsortedConnections.length > 0) { 9287 var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen 9288 reverse = desc === "right" || desc === "top", 9289 anchors = placeAnchorsOnLine(desc, elementDimensions, 9290 elementPosition, sc, 9291 isHorizontal, otherMultiplier, reverse); 9292 9293 // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. 9294 var _setAnchorLocation = function (endpoint, anchorPos) { 9295 continuousAnchorLocations[endpoint.id] = [ anchorPos[0], anchorPos[1], anchorPos[2], anchorPos[3] ]; 9296 continuousAnchorOrientations[endpoint.id] = orientation; 9297 }; 9298 9299 for (var i = 0; i < anchors.length; i++) { 9300 var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; 9301 if (weAreSource) { 9302 _setAnchorLocation(c.endpoints[0], anchors[i]); 9303 } 9304 if (weAreTarget) { 9305 _setAnchorLocation(c.endpoints[1], anchors[i]); 9306 } 9307 } 9308 } 9309 }; 9310 9311 placeSomeAnchors("bottom", sS, [sO.left, sO.top], _anchorLists.bottom, true, 1, [0, 1]); 9312 placeSomeAnchors("top", sS, [sO.left, sO.top], _anchorLists.top, true, 0, [0, -1]); 9313 placeSomeAnchors("left", sS, [sO.left, sO.top], _anchorLists.left, false, 0, [-1, 0]); 9314 placeSomeAnchors("right", sS, [sO.left, sO.top], _anchorLists.right, false, 1, [1, 0]); 9315 }; 9316 9317 this.reset = function () { 9318 _amEndpoints = {}; 9319 connectionsByElementId = {}; 9320 anchorLists = {}; 9321 }; 9322 this.addFloatingConnection = function (key, conn) { 9323 floatingConnections[key] = conn; 9324 }; 9325 this.removeFloatingConnection = function (key) { 9326 delete floatingConnections[key]; 9327 }; 9328 this.newConnection = function (conn) { 9329 var sourceId = conn.sourceId, targetId = conn.targetId, 9330 ep = conn.endpoints, 9331 doRegisterTarget = true, 9332 registerConnection = function (otherIndex, otherEndpoint, otherAnchor, elId, c) { 9333 if ((sourceId === targetId) && otherAnchor.isContinuous) { 9334 // remove the target endpoint's canvas. we dont need it. 9335 conn._jsPlumb.instance.removeElement(ep[1].canvas); 9336 doRegisterTarget = false; 9337 } 9338 _ju.addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor === _jp.DynamicAnchor]); 9339 }; 9340 9341 registerConnection(0, ep[0], ep[0].anchor, targetId, conn); 9342 if (doRegisterTarget) { 9343 registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); 9344 } 9345 }; 9346 var removeEndpointFromAnchorLists = function (endpoint) { 9347 (function (list, eId) { 9348 if (list) { // transient anchors dont get entries in this list. 9349 var f = function (e) { 9350 return e[4] === eId; 9351 }; 9352 _ju.removeWithFunction(list.top, f); 9353 _ju.removeWithFunction(list.left, f); 9354 _ju.removeWithFunction(list.bottom, f); 9355 _ju.removeWithFunction(list.right, f); 9356 } 9357 })(anchorLists[endpoint.elementId], endpoint.id); 9358 }; 9359 this.connectionDetached = function (connInfo, doNotRedraw) { 9360 var connection = connInfo.connection || connInfo, 9361 sourceId = connInfo.sourceId, 9362 targetId = connInfo.targetId, 9363 ep = connection.endpoints, 9364 removeConnection = function (otherIndex, otherEndpoint, otherAnchor, elId, c) { 9365 _ju.removeWithFunction(connectionsByElementId[elId], function (_c) { 9366 return _c[0].id === c.id; 9367 }); 9368 }; 9369 9370 removeConnection(1, ep[1], ep[1].anchor, sourceId, connection); 9371 removeConnection(0, ep[0], ep[0].anchor, targetId, connection); 9372 if (connection.floatingId) { 9373 removeConnection(connection.floatingIndex, connection.floatingEndpoint, connection.floatingEndpoint.anchor, connection.floatingId, connection); 9374 removeEndpointFromAnchorLists(connection.floatingEndpoint); 9375 } 9376 9377 // remove from anchorLists 9378 removeEndpointFromAnchorLists(connection.endpoints[0]); 9379 removeEndpointFromAnchorLists(connection.endpoints[1]); 9380 9381 if (!doNotRedraw) { 9382 self.redraw(connection.sourceId); 9383 if (connection.targetId !== connection.sourceId) { 9384 self.redraw(connection.targetId); 9385 } 9386 } 9387 }; 9388 this.add = function (endpoint, elementId) { 9389 _ju.addToList(_amEndpoints, elementId, endpoint); 9390 }; 9391 this.changeId = function (oldId, newId) { 9392 connectionsByElementId[newId] = connectionsByElementId[oldId]; 9393 _amEndpoints[newId] = _amEndpoints[oldId]; 9394 delete connectionsByElementId[oldId]; 9395 delete _amEndpoints[oldId]; 9396 }; 9397 this.getConnectionsFor = function (elementId) { 9398 return connectionsByElementId[elementId] || []; 9399 }; 9400 this.getEndpointsFor = function (elementId) { 9401 return _amEndpoints[elementId] || []; 9402 }; 9403 this.deleteEndpoint = function (endpoint) { 9404 _ju.removeWithFunction(_amEndpoints[endpoint.elementId], function (e) { 9405 return e.id === endpoint.id; 9406 }); 9407 removeEndpointFromAnchorLists(endpoint); 9408 }; 9409 this.clearFor = function (elementId) { 9410 delete _amEndpoints[elementId]; 9411 _amEndpoints[elementId] = []; 9412 }; 9413 // updates the given anchor list by either updating an existing anchor's info, or adding it. this function 9414 // also removes the anchor from its previous list, if the edge it is on has changed. 9415 // all connections found along the way (those that are connected to one of the faces this function 9416 // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint 9417 // them wthout having to calculate anything else about them. 9418 var _updateAnchorList = function (lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { 9419 // first try to find the exact match, but keep track of the first index of a matching element id along the way.s 9420 var exactIdx = -1, 9421 firstMatchingElIdx = -1, 9422 endpoint = conn.endpoints[idx], 9423 endpointId = endpoint.id, 9424 oIdx = [1, 0][idx], 9425 values = [ 9426 [ theta, order ], 9427 conn, 9428 aBoolean, 9429 otherElId, 9430 endpointId 9431 ], 9432 listToAddTo = lists[edgeId], 9433 listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null, 9434 i, 9435 candidate; 9436 9437 if (listToRemoveFrom) { 9438 var rIdx = _ju.findWithFunction(listToRemoveFrom, function (e) { 9439 return e[4] === endpointId; 9440 }); 9441 if (rIdx !== -1) { 9442 listToRemoveFrom.splice(rIdx, 1); 9443 // get all connections from this list 9444 for (i = 0; i < listToRemoveFrom.length; i++) { 9445 candidate = listToRemoveFrom[i][1]; 9446 _ju.addWithFunction(connsToPaint, candidate, function (c) { 9447 return c.id === candidate.id; 9448 }); 9449 _ju.addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function (e) { 9450 return e.id === candidate.endpoints[idx].id; 9451 }); 9452 _ju.addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[oIdx], function (e) { 9453 return e.id === candidate.endpoints[oIdx].id; 9454 }); 9455 } 9456 } 9457 } 9458 9459 for (i = 0; i < listToAddTo.length; i++) { 9460 candidate = listToAddTo[i][1]; 9461 if (params.idx === 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx === -1) { 9462 firstMatchingElIdx = i; 9463 } 9464 _ju.addWithFunction(connsToPaint, candidate, function (c) { 9465 return c.id === candidate.id; 9466 }); 9467 _ju.addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function (e) { 9468 return e.id === candidate.endpoints[idx].id; 9469 }); 9470 _ju.addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[oIdx], function (e) { 9471 return e.id === candidate.endpoints[oIdx].id; 9472 }); 9473 } 9474 if (exactIdx !== -1) { 9475 listToAddTo[exactIdx] = values; 9476 } 9477 else { 9478 var insertIdx = reverse ? firstMatchingElIdx !== -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. 9479 listToAddTo.splice(insertIdx, 0, values); 9480 } 9481 9482 // store this for next time. 9483 endpoint._continuousAnchorEdge = edgeId; 9484 }; 9485 9486 // 9487 // find the entry in an endpoint's list for this connection and update its target endpoint 9488 // with the current target in the connection. 9489 // This method and sourceChanged need to be folder into one. 9490 // 9491 this.updateOtherEndpoint = function (sourceElId, oldTargetId, newTargetId, connection) { 9492 var sIndex = _ju.findWithFunction(connectionsByElementId[sourceElId], function (i) { 9493 return i[0].id === connection.id; 9494 }), 9495 tIndex = _ju.findWithFunction(connectionsByElementId[oldTargetId], function (i) { 9496 return i[0].id === connection.id; 9497 }); 9498 9499 // update or add data for source 9500 if (sIndex !== -1) { 9501 connectionsByElementId[sourceElId][sIndex][0] = connection; 9502 connectionsByElementId[sourceElId][sIndex][1] = connection.endpoints[1]; 9503 connectionsByElementId[sourceElId][sIndex][2] = connection.endpoints[1].anchor.constructor === _jp.DynamicAnchor; 9504 } 9505 9506 // remove entry for previous target (if there) 9507 if (tIndex > -1) { 9508 connectionsByElementId[oldTargetId].splice(tIndex, 1); 9509 // add entry for new target 9510 _ju.addToList(connectionsByElementId, newTargetId, [connection, connection.endpoints[0], connection.endpoints[0].anchor.constructor === _jp.DynamicAnchor]); 9511 } 9512 9513 connection.updateConnectedClass(); 9514 }; 9515 9516 // 9517 // notification that the connection given has changed source from the originalId to the newId. 9518 // This involves: 9519 // 1. removing the connection from the list of connections stored for the originalId 9520 // 2. updating the source information for the target of the connection 9521 // 3. re-registering the connection in connectionsByElementId with the newId 9522 // 9523 this.sourceChanged = function (originalId, newId, connection, newElement) { 9524 if (originalId !== newId) { 9525 9526 connection.sourceId = newId; 9527 connection.source = newElement; 9528 9529 // remove the entry that points from the old source to the target 9530 _ju.removeWithFunction(connectionsByElementId[originalId], function (info) { 9531 return info[0].id === connection.id; 9532 }); 9533 // find entry for target and update it 9534 var tIdx = _ju.findWithFunction(connectionsByElementId[connection.targetId], function (i) { 9535 return i[0].id === connection.id; 9536 }); 9537 if (tIdx > -1) { 9538 connectionsByElementId[connection.targetId][tIdx][0] = connection; 9539 connectionsByElementId[connection.targetId][tIdx][1] = connection.endpoints[0]; 9540 connectionsByElementId[connection.targetId][tIdx][2] = connection.endpoints[0].anchor.constructor === _jp.DynamicAnchor; 9541 } 9542 // add entry for new source 9543 _ju.addToList(connectionsByElementId, newId, [connection, connection.endpoints[1], connection.endpoints[1].anchor.constructor === _jp.DynamicAnchor]); 9544 9545 // TODO SP not final on this yet. when a user drags an existing connection and it turns into a self 9546 // loop, then this code hides the target endpoint (by removing it from the DOM) But I think this should 9547 // occur only if the anchor is Continuous 9548 if (connection.endpoints[1].anchor.isContinuous) { 9549 if (connection.source === connection.target) { 9550 connection._jsPlumb.instance.removeElement(connection.endpoints[1].canvas); 9551 } 9552 else { 9553 if (connection.endpoints[1].canvas.parentNode == null) { 9554 connection._jsPlumb.instance.appendElement(connection.endpoints[1].canvas); 9555 } 9556 } 9557 } 9558 9559 connection.updateConnectedClass(); 9560 } 9561 }; 9562 9563 // 9564 // moves the given endpoint from `currentId` to `element`. 9565 // This involves: 9566 // 9567 // 1. changing the key in _amEndpoints under which the endpoint is stored 9568 // 2. changing the source or target values in all of the endpoint's connections 9569 // 3. changing the array in connectionsByElementId in which the endpoint's connections 9570 // are stored (done by either sourceChanged or updateOtherEndpoint) 9571 // 9572 this.rehomeEndpoint = function (ep, currentId, element) { 9573 var eps = _amEndpoints[currentId] || [], 9574 elementId = jsPlumbInstance.getId(element); 9575 9576 if (elementId !== currentId) { 9577 var idx = eps.indexOf(ep); 9578 if (idx > -1) { 9579 var _ep = eps.splice(idx, 1)[0]; 9580 self.add(_ep, elementId); 9581 } 9582 } 9583 9584 for (var i = 0; i < ep.connections.length; i++) { 9585 if (ep.connections[i].sourceId === currentId) { 9586 self.sourceChanged(currentId, ep.elementId, ep.connections[i], ep.element); 9587 } 9588 else if (ep.connections[i].targetId === currentId) { 9589 ep.connections[i].targetId = ep.elementId; 9590 ep.connections[i].target = ep.element; 9591 self.updateOtherEndpoint(ep.connections[i].sourceId, currentId, ep.elementId, ep.connections[i]); 9592 } 9593 } 9594 }; 9595 9596 this.redraw = function (elementId, ui, timestamp, offsetToUI, clearEdits, doNotRecalcEndpoint) { 9597 9598 if (!jsPlumbInstance.isSuspendDrawing()) { 9599 // get all the endpoints for this element 9600 var ep = _amEndpoints[elementId] || [], 9601 endpointConnections = connectionsByElementId[elementId] || [], 9602 connectionsToPaint = [], 9603 endpointsToPaint = [], 9604 anchorsToUpdate = []; 9605 9606 timestamp = timestamp || jsPlumbInstance.timestamp(); 9607 // offsetToUI are values that would have been calculated in the dragManager when registering 9608 // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been 9609 // registered as draggable. 9610 offsetToUI = offsetToUI || {left: 0, top: 0}; 9611 if (ui) { 9612 ui = { 9613 left: ui.left + offsetToUI.left, 9614 top: ui.top + offsetToUI.top 9615 }; 9616 } 9617 9618 // valid for one paint cycle. 9619 var myOffset = jsPlumbInstance.updateOffset({ elId: elementId, offset: ui, recalc: false, timestamp: timestamp }), 9620 orientationCache = {}; 9621 9622 // actually, first we should compute the orientation of this element to all other elements to which 9623 // this element is connected with a continuous anchor (whether both ends of the connection have 9624 // a continuous anchor or just one) 9625 9626 for (var i = 0; i < endpointConnections.length; i++) { 9627 var conn = endpointConnections[i][0], 9628 sourceId = conn.sourceId, 9629 targetId = conn.targetId, 9630 sourceContinuous = conn.endpoints[0].anchor.isContinuous, 9631 targetContinuous = conn.endpoints[1].anchor.isContinuous; 9632 9633 if (sourceContinuous || targetContinuous) { 9634 var oKey = sourceId + "_" + targetId, 9635 o = orientationCache[oKey], 9636 oIdx = conn.sourceId === elementId ? 1 : 0; 9637 9638 if (sourceContinuous && !anchorLists[sourceId]) { 9639 anchorLists[sourceId] = { top: [], right: [], bottom: [], left: [] }; 9640 } 9641 if (targetContinuous && !anchorLists[targetId]) { 9642 anchorLists[targetId] = { top: [], right: [], bottom: [], left: [] }; 9643 } 9644 9645 if (elementId !== targetId) { 9646 jsPlumbInstance.updateOffset({ elId: targetId, timestamp: timestamp }); 9647 } 9648 if (elementId !== sourceId) { 9649 jsPlumbInstance.updateOffset({ elId: sourceId, timestamp: timestamp }); 9650 } 9651 9652 var td = jsPlumbInstance.getCachedData(targetId), 9653 sd = jsPlumbInstance.getCachedData(sourceId); 9654 9655 if (targetId === sourceId && (sourceContinuous || targetContinuous)) { 9656 // here we may want to improve this by somehow determining the face we'd like 9657 // to put the connector on. ideally, when drawing, the face should be calculated 9658 // by determining which face is closest to the point at which the mouse button 9659 // was released. for now, we're putting it on the top face. 9660 _updateAnchorList( anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint); 9661 _updateAnchorList( anchorLists[targetId], -Math.PI / 2, 0, conn, false, sourceId, 1, false, "top", targetId, connectionsToPaint, endpointsToPaint); 9662 } 9663 else { 9664 if (!o) { 9665 o = calculateOrientation(sourceId, targetId, sd.o, td.o, conn.endpoints[0].anchor, conn.endpoints[1].anchor); 9666 orientationCache[oKey] = o; 9667 // this would be a performance enhancement, but the computed angles need to be clamped to 9668 //the (-PI/2 -> PI/2) range in order for the sorting to work properly. 9669 /* orientationCache[oKey2] = { 9670 orientation:o.orientation, 9671 a:[o.a[1], o.a[0]], 9672 theta:o.theta + Math.PI, 9673 theta2:o.theta2 + Math.PI 9674 };*/ 9675 } 9676 if (sourceContinuous) { 9677 _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); 9678 } 9679 if (targetContinuous) { 9680 _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); 9681 } 9682 } 9683 9684 if (sourceContinuous) { 9685 _ju.addWithFunction(anchorsToUpdate, sourceId, function (a) { 9686 return a === sourceId; 9687 }); 9688 } 9689 if (targetContinuous) { 9690 _ju.addWithFunction(anchorsToUpdate, targetId, function (a) { 9691 return a === targetId; 9692 }); 9693 } 9694 _ju.addWithFunction(connectionsToPaint, conn, function (c) { 9695 return c.id === conn.id; 9696 }); 9697 if ((sourceContinuous && oIdx === 0) || (targetContinuous && oIdx === 1)) { 9698 _ju.addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function (e) { 9699 return e.id === conn.endpoints[oIdx].id; 9700 }); 9701 } 9702 } 9703 } 9704 9705 // place Endpoints whose anchors are continuous but have no Connections 9706 for (i = 0; i < ep.length; i++) { 9707 if (ep[i].connections.length === 0 && ep[i].anchor.isContinuous) { 9708 if (!anchorLists[elementId]) { 9709 anchorLists[elementId] = { top: [], right: [], bottom: [], left: [] }; 9710 } 9711 _updateAnchorList(anchorLists[elementId], -Math.PI / 2, 0, {endpoints: [ep[i], ep[i]], paint: function () { 9712 }}, false, elementId, 0, false, ep[i].anchor.getDefaultFace(), elementId, connectionsToPaint, endpointsToPaint); 9713 _ju.addWithFunction(anchorsToUpdate, elementId, function (a) { 9714 return a === elementId; 9715 }); 9716 } 9717 } 9718 9719 // now place all the continuous anchors we need to; 9720 for (i = 0; i < anchorsToUpdate.length; i++) { 9721 placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); 9722 } 9723 9724 // now that continuous anchors have been placed, paint all the endpoints for this element 9725 // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next 9726 // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. 9727 for (i = 0; i < ep.length; i++) { 9728 ep[i].paint({ timestamp: timestamp, offset: myOffset, dimensions: myOffset.s, recalc: doNotRecalcEndpoint !== true }); 9729 } 9730 9731 // ... and any other endpoints we came across as a result of the continuous anchors. 9732 for (i = 0; i < endpointsToPaint.length; i++) { 9733 var cd = jsPlumbInstance.getCachedData(endpointsToPaint[i].elementId); 9734 endpointsToPaint[i].paint({ timestamp: timestamp, offset: cd, dimensions: cd.s }); 9735 } 9736 9737 // paint all the standard and "dynamic connections", which are connections whose other anchor is 9738 // static and therefore does need to be recomputed; we make sure that happens only one time. 9739 9740 // TODO we could have compiled a list of these in the first pass through connections; might save some time. 9741 for (i = 0; i < endpointConnections.length; i++) { 9742 var otherEndpoint = endpointConnections[i][1]; 9743 if (otherEndpoint.anchor.constructor === _jp.DynamicAnchor) { 9744 otherEndpoint.paint({ elementWithPrecedence: elementId, timestamp: timestamp }); 9745 _ju.addWithFunction(connectionsToPaint, endpointConnections[i][0], function (c) { 9746 return c.id === endpointConnections[i][0].id; 9747 }); 9748 // all the connections for the other endpoint now need to be repainted 9749 for (var k = 0; k < otherEndpoint.connections.length; k++) { 9750 if (otherEndpoint.connections[k] !== endpointConnections[i][0]) { 9751 _ju.addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function (c) { 9752 return c.id === otherEndpoint.connections[k].id; 9753 }); 9754 } 9755 } 9756 } else if (otherEndpoint.anchor.constructor === _jp.Anchor) { 9757 _ju.addWithFunction(connectionsToPaint, endpointConnections[i][0], function (c) { 9758 return c.id === endpointConnections[i][0].id; 9759 }); 9760 } 9761 } 9762 9763 // paint current floating connection for this element, if there is one. 9764 var fc = floatingConnections[elementId]; 9765 if (fc) { 9766 fc.paint({timestamp: timestamp, recalc: false, elId: elementId}); 9767 } 9768 9769 // paint all the connections 9770 for (i = 0; i < connectionsToPaint.length; i++) { 9771 connectionsToPaint[i].paint({elId: elementId, timestamp: timestamp, recalc: false, clearEdits: clearEdits}); 9772 } 9773 } 9774 }; 9775 9776 var ContinuousAnchor = function (anchorParams) { 9777 _ju.EventGenerator.apply(this); 9778 this.type = "Continuous"; 9779 this.isDynamic = true; 9780 this.isContinuous = true; 9781 var faces = anchorParams.faces || ["top", "right", "bottom", "left"], 9782 clockwise = !(anchorParams.clockwise === false), 9783 availableFaces = { }, 9784 opposites = { "top": "bottom", "right": "left", "left": "right", "bottom": "top" }, 9785 clockwiseOptions = { "top": "right", "right": "bottom", "left": "top", "bottom": "left" }, 9786 antiClockwiseOptions = { "top": "left", "right": "top", "left": "bottom", "bottom": "right" }, 9787 secondBest = clockwise ? clockwiseOptions : antiClockwiseOptions, 9788 lastChoice = clockwise ? antiClockwiseOptions : clockwiseOptions, 9789 cssClass = anchorParams.cssClass || ""; 9790 9791 for (var i = 0; i < faces.length; i++) { 9792 availableFaces[faces[i]] = true; 9793 } 9794 9795 this.getDefaultFace = function () { 9796 return faces.length === 0 ? "top" : faces[0]; 9797 }; 9798 9799 // if the given edge is supported, returns it. otherwise looks for a substitute that _is_ 9800 // supported. if none supported we also return the request edge. 9801 this.verifyEdge = function (edge) { 9802 if (availableFaces[edge]) { 9803 return edge; 9804 } 9805 else if (availableFaces[opposites[edge]]) { 9806 return opposites[edge]; 9807 } 9808 else if (availableFaces[secondBest[edge]]) { 9809 return secondBest[edge]; 9810 } 9811 else if (availableFaces[lastChoice[edge]]) { 9812 return lastChoice[edge]; 9813 } 9814 return edge; // we have to give them something. 9815 }; 9816 9817 this.isEdgeSupported = function (edge) { 9818 return availableFaces[edge] === true; 9819 }; 9820 9821 this.compute = function (params) { 9822 return userDefinedContinuousAnchorLocations[params.element.id] || continuousAnchorLocations[params.element.id] || [0, 0]; 9823 }; 9824 this.getCurrentLocation = function (params) { 9825 return userDefinedContinuousAnchorLocations[params.element.id] || continuousAnchorLocations[params.element.id] || [0, 0]; 9826 }; 9827 this.getOrientation = function (endpoint) { 9828 return continuousAnchorOrientations[endpoint.id] || [0, 0]; 9829 }; 9830 this.clearUserDefinedLocation = function () { 9831 delete userDefinedContinuousAnchorLocations[anchorParams.elementId]; 9832 }; 9833 this.setUserDefinedLocation = function (loc) { 9834 userDefinedContinuousAnchorLocations[anchorParams.elementId] = loc; 9835 }; 9836 this.getCssClass = function () { 9837 return cssClass; 9838 }; 9839 }; 9840 9841 // continuous anchors 9842 jsPlumbInstance.continuousAnchorFactory = { 9843 get: function (params) { 9844 return new ContinuousAnchor(params); 9845 }, 9846 clear: function (elementId) { 9847 delete userDefinedContinuousAnchorLocations[elementId]; 9848 delete continuousAnchorLocations[elementId]; 9849 } 9850 }; 9851 }; 9852 9853 /** 9854 * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user 9855 * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), 9856 * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the 9857 * creation of Anchors without user intervention. 9858 */ 9859 _jp.Anchor = function (params) { 9860 this.x = params.x || 0; 9861 this.y = params.y || 0; 9862 this.elementId = params.elementId; 9863 this.cssClass = params.cssClass || ""; 9864 this.userDefinedLocation = null; 9865 this.orientation = params.orientation || [ 0, 0 ]; 9866 this.lastReturnValue = null; 9867 this.offsets = params.offsets || [ 0, 0 ]; 9868 this.timestamp = null; 9869 9870 _ju.EventGenerator.apply(this); 9871 9872 this.compute = function (params) { 9873 9874 var xy = params.xy, wh = params.wh, timestamp = params.timestamp; 9875 9876 if (params.clearUserDefinedLocation) { 9877 this.userDefinedLocation = null; 9878 } 9879 9880 if (timestamp && timestamp === this.timestamp) { 9881 return this.lastReturnValue; 9882 } 9883 9884 if (this.userDefinedLocation != null) { 9885 this.lastReturnValue = this.userDefinedLocation; 9886 } 9887 else { 9888 this.lastReturnValue = [ xy[0] + (this.x * wh[0]) + this.offsets[0], xy[1] + (this.y * wh[1]) + this.offsets[1] ]; 9889 } 9890 9891 this.timestamp = timestamp; 9892 return this.lastReturnValue; 9893 }; 9894 9895 this.getCurrentLocation = function (params) { 9896 params = params || {}; 9897 return (this.lastReturnValue == null || (params.timestamp != null && this.timestamp !== params.timestamp)) ? this.compute(params) : this.lastReturnValue; 9898 }; 9899 }; 9900 _ju.extend(_jp.Anchor, _ju.EventGenerator, { 9901 equals: function (anchor) { 9902 if (!anchor) { 9903 return false; 9904 } 9905 var ao = anchor.getOrientation(), 9906 o = this.getOrientation(); 9907 return this.x === anchor.x && this.y === anchor.y && this.offsets[0] === anchor.offsets[0] && this.offsets[1] === anchor.offsets[1] && o[0] === ao[0] && o[1] === ao[1]; 9908 }, 9909 getUserDefinedLocation: function () { 9910 return this.userDefinedLocation; 9911 }, 9912 setUserDefinedLocation: function (l) { 9913 this.userDefinedLocation = l; 9914 }, 9915 clearUserDefinedLocation: function () { 9916 this.userDefinedLocation = null; 9917 }, 9918 getOrientation: function () { 9919 return this.orientation; 9920 }, 9921 getCssClass: function () { 9922 return this.cssClass; 9923 } 9924 }); 9925 9926 /** 9927 * An Anchor that floats. its orientation is computed dynamically from 9928 * its position relative to the anchor it is floating relative to. It is used when creating 9929 * a connection through drag and drop. 9930 * 9931 * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. 9932 */ 9933 _jp.FloatingAnchor = function (params) { 9934 9935 _jp.Anchor.apply(this, arguments); 9936 9937 // this is the anchor that this floating anchor is referenced to for 9938 // purposes of calculating the orientation. 9939 var ref = params.reference, 9940 // the canvas this refers to. 9941 refCanvas = params.referenceCanvas, 9942 size = _jp.getSize(refCanvas), 9943 // these are used to store the current relative position of our 9944 // anchor wrt the reference anchor. they only indicate 9945 // direction, so have a value of 1 or -1 (or, very rarely, 0). these 9946 // values are written by the compute method, and read 9947 // by the getOrientation method. 9948 xDir = 0, yDir = 0, 9949 // temporary member used to store an orientation when the floating 9950 // anchor is hovering over another anchor. 9951 orientation = null, 9952 _lastResult = null; 9953 9954 // clear from parent. we want floating anchor orientation to always be computed. 9955 this.orientation = null; 9956 9957 // set these to 0 each; they are used by certain types of connectors in the loopback case, 9958 // when the connector is trying to clear the element it is on. but for floating anchor it's not 9959 // very important. 9960 this.x = 0; 9961 this.y = 0; 9962 9963 this.isFloating = true; 9964 9965 this.compute = function (params) { 9966 var xy = params.xy, 9967 result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. 9968 _lastResult = result; 9969 return result; 9970 }; 9971 9972 this.getOrientation = function (_endpoint) { 9973 if (orientation) { 9974 return orientation; 9975 } 9976 else { 9977 var o = ref.getOrientation(_endpoint); 9978 // here we take into account the orientation of the other 9979 // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come 9980 // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. 9981 return [ Math.abs(o[0]) * xDir * -1, 9982 Math.abs(o[1]) * yDir * -1 ]; 9983 } 9984 }; 9985 9986 /** 9987 * notification the endpoint associated with this anchor is hovering 9988 * over another anchor; we want to assume that anchor's orientation 9989 * for the duration of the hover. 9990 */ 9991 this.over = function (anchor, endpoint) { 9992 orientation = anchor.getOrientation(endpoint); 9993 }; 9994 9995 /** 9996 * notification the endpoint associated with this anchor is no 9997 * longer hovering over another anchor; we should resume calculating 9998 * orientation as we normally do. 9999 */ 10000 this.out = function () { 10001 orientation = null; 10002 }; 10003 10004 this.getCurrentLocation = function (params) { 10005 return _lastResult == null ? this.compute(params) : _lastResult; 10006 }; 10007 }; 10008 _ju.extend(_jp.FloatingAnchor, _jp.Anchor); 10009 10010 var _convertAnchor = function (anchor, jsPlumbInstance, elementId) { 10011 return anchor.constructor === _jp.Anchor ? anchor : jsPlumbInstance.makeAnchor(anchor, elementId, jsPlumbInstance); 10012 }; 10013 10014 /* 10015 * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles 10016 * through at compute time to find the one that is located closest to 10017 * the center of the target element, and returns that Anchor's compute 10018 * method result. this causes endpoints to follow each other with 10019 * respect to the orientation of their target elements, which is a useful 10020 * feature for some applications. 10021 * 10022 */ 10023 _jp.DynamicAnchor = function (params) { 10024 _jp.Anchor.apply(this, arguments); 10025 10026 this.isDynamic = true; 10027 this.anchors = []; 10028 this.elementId = params.elementId; 10029 this.jsPlumbInstance = params.jsPlumbInstance; 10030 10031 for (var i = 0; i < params.anchors.length; i++) { 10032 this.anchors[i] = _convertAnchor(params.anchors[i], this.jsPlumbInstance, this.elementId); 10033 } 10034 10035 this.getAnchors = function () { 10036 return this.anchors; 10037 }; 10038 this.locked = false; 10039 var _curAnchor = this.anchors.length > 0 ? this.anchors[0] : null, 10040 _lastAnchor = _curAnchor, 10041 self = this, 10042 10043 // helper method to calculate the distance between the centers of the two elements. 10044 _distance = function (anchor, cx, cy, xy, wh) { 10045 var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]), 10046 acx = xy[0] + (wh[0] / 2), acy = xy[1] + (wh[1] / 2); 10047 return (Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)) + 10048 Math.sqrt(Math.pow(acx - ax, 2) + Math.pow(acy - ay, 2))); 10049 }, 10050 // default method uses distance between element centers. you can provide your own method in the dynamic anchor 10051 // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: 10052 // xy - xy loc of the anchor's element 10053 // wh - anchor's element's dimensions 10054 // txy - xy loc of the element of the other anchor in the connection 10055 // twh - dimensions of the element of the other anchor in the connection. 10056 // anchors - the list of selectable anchors 10057 _anchorSelector = params.selector || function (xy, wh, txy, twh, anchors) { 10058 var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); 10059 var minIdx = -1, minDist = Infinity; 10060 for (var i = 0; i < anchors.length; i++) { 10061 var d = _distance(anchors[i], cx, cy, xy, wh); 10062 if (d < minDist) { 10063 minIdx = i + 0; 10064 minDist = d; 10065 } 10066 } 10067 return anchors[minIdx]; 10068 }; 10069 10070 this.compute = function (params) { 10071 var xy = params.xy, wh = params.wh, txy = params.txy, twh = params.twh; 10072 10073 this.timestamp = params.timestamp; 10074 10075 var udl = self.getUserDefinedLocation(); 10076 if (udl != null) { 10077 return udl; 10078 } 10079 10080 // if anchor is locked or an opposite element was not given, we 10081 // maintain our state. anchor will be locked 10082 // if it is the source of a drag and drop. 10083 if (this.locked || txy == null || twh == null) { 10084 return _curAnchor.compute(params); 10085 } 10086 else { 10087 params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. 10088 } 10089 10090 _curAnchor = _anchorSelector(xy, wh, txy, twh, this.anchors); 10091 this.x = _curAnchor.x; 10092 this.y = _curAnchor.y; 10093 10094 if (_curAnchor !== _lastAnchor) { 10095 this.fire("anchorChanged", _curAnchor); 10096 } 10097 10098 _lastAnchor = _curAnchor; 10099 10100 return _curAnchor.compute(params); 10101 }; 10102 10103 this.getCurrentLocation = function (params) { 10104 return this.getUserDefinedLocation() || (_curAnchor != null ? _curAnchor.getCurrentLocation(params) : null); 10105 }; 10106 10107 this.getOrientation = function (_endpoint) { 10108 return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; 10109 }; 10110 this.over = function (anchor, endpoint) { 10111 if (_curAnchor != null) { 10112 _curAnchor.over(anchor, endpoint); 10113 } 10114 }; 10115 this.out = function () { 10116 if (_curAnchor != null) { 10117 _curAnchor.out(); 10118 } 10119 }; 10120 10121 this.getCssClass = function () { 10122 return (_curAnchor && _curAnchor.getCssClass()) || ""; 10123 }; 10124 }; 10125 _ju.extend(_jp.DynamicAnchor, _jp.Anchor); 10126 10127 // -------- basic anchors ------------------ 10128 var _curryAnchor = function (x, y, ox, oy, type, fnInit) { 10129 _jp.Anchors[type] = function (params) { 10130 var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); 10131 a.type = type; 10132 if (fnInit) { 10133 fnInit(a, params); 10134 } 10135 return a; 10136 }; 10137 }; 10138 10139 _curryAnchor(0.5, 0, 0, -1, "TopCenter"); 10140 _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); 10141 _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); 10142 _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); 10143 10144 _curryAnchor(0.5, 0, 0, -1, "Top"); 10145 _curryAnchor(0.5, 1, 0, 1, "Bottom"); 10146 _curryAnchor(0, 0.5, -1, 0, "Left"); 10147 _curryAnchor(1, 0.5, 1, 0, "Right"); 10148 _curryAnchor(0.5, 0.5, 0, 0, "Center"); 10149 _curryAnchor(1, 0, 0, -1, "TopRight"); 10150 _curryAnchor(1, 1, 0, 1, "BottomRight"); 10151 _curryAnchor(0, 0, 0, -1, "TopLeft"); 10152 _curryAnchor(0, 1, 0, 1, "BottomLeft"); 10153 10154 // ------- dynamic anchors ------------------- 10155 10156 // default dynamic anchors chooses from Top, Right, Bottom, Left 10157 _jp.Defaults.DynamicAnchors = function (params) { 10158 return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); 10159 }; 10160 10161 // default dynamic anchors bound to name 'AutoDefault' 10162 _jp.Anchors.AutoDefault = function (params) { 10163 var a = params.jsPlumbInstance.makeDynamicAnchor(_jp.Defaults.DynamicAnchors(params)); 10164 a.type = "AutoDefault"; 10165 return a; 10166 }; 10167 10168 // ------- continuous anchors ------------------- 10169 10170 var _curryContinuousAnchor = function (type, faces) { 10171 _jp.Anchors[type] = function (params) { 10172 var a = params.jsPlumbInstance.makeAnchor(["Continuous", { faces: faces }], params.elementId, params.jsPlumbInstance); 10173 a.type = type; 10174 return a; 10175 }; 10176 }; 10177 10178 _jp.Anchors.Continuous = function (params) { 10179 return params.jsPlumbInstance.continuousAnchorFactory.get(params); 10180 }; 10181 10182 _curryContinuousAnchor("ContinuousLeft", ["left"]); 10183 _curryContinuousAnchor("ContinuousTop", ["top"]); 10184 _curryContinuousAnchor("ContinuousBottom", ["bottom"]); 10185 _curryContinuousAnchor("ContinuousRight", ["right"]); 10186 10187 // ------- position assign anchors ------------------- 10188 10189 // this anchor type lets you assign the position at connection time. 10190 _curryAnchor(0, 0, 0, 0, "Assign", function (anchor, params) { 10191 // find what to use as the "position finder". the user may have supplied a String which represents 10192 // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the 10193 // position finder as a function. we find out what to use and then set it on the anchor. 10194 var pf = params.position || "Fixed"; 10195 anchor.positionFinder = pf.constructor === String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; 10196 // always set the constructor params; the position finder might need them later (the Grid one does, 10197 // for example) 10198 anchor.constructorParams = params; 10199 }); 10200 10201 // these are the default anchor positions finders, which are used by the makeTarget function. supplying 10202 // a position finder argument to that function allows you to specify where the resulting anchor will 10203 // be located 10204 root.jsPlumbInstance.prototype.AnchorPositionFinders = { 10205 "Fixed": function (dp, ep, es) { 10206 return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; 10207 }, 10208 "Grid": function (dp, ep, es, params) { 10209 var dx = dp.left - ep.left, dy = dp.top - ep.top, 10210 gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]), 10211 mx = Math.floor(dx / gx), my = Math.floor(dy / gy); 10212 return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; 10213 } 10214 }; 10215 10216 // ------- perimeter anchors ------------------- 10217 10218 _jp.Anchors.Perimeter = function (params) { 10219 params = params || {}; 10220 var anchorCount = params.anchorCount || 60, 10221 shape = params.shape; 10222 10223 if (!shape) { 10224 throw new Error("no shape supplied to Perimeter Anchor type"); 10225 } 10226 10227 var _circle = function () { 10228 var r = 0.5, step = Math.PI * 2 / anchorCount, current = 0, a = []; 10229 for (var i = 0; i < anchorCount; i++) { 10230 var x = r + (r * Math.sin(current)), 10231 y = r + (r * Math.cos(current)); 10232 a.push([ x, y, 0, 0 ]); 10233 current += step; 10234 } 10235 return a; 10236 }, 10237 _path = function (segments) { 10238 var anchorsPerFace = anchorCount / segments.length, a = [], 10239 _computeFace = function (x1, y1, x2, y2, fractionalLength) { 10240 anchorsPerFace = anchorCount * fractionalLength; 10241 var dx = (x2 - x1) / anchorsPerFace, dy = (y2 - y1) / anchorsPerFace; 10242 for (var i = 0; i < anchorsPerFace; i++) { 10243 a.push([ 10244 x1 + (dx * i), 10245 y1 + (dy * i), 10246 0, 10247 0 10248 ]); 10249 } 10250 }; 10251 10252 for (var i = 0; i < segments.length; i++) { 10253 _computeFace.apply(null, segments[i]); 10254 } 10255 10256 return a; 10257 }, 10258 _shape = function (faces) { 10259 var s = []; 10260 for (var i = 0; i < faces.length; i++) { 10261 s.push([faces[i][0], faces[i][1], faces[i][2], faces[i][3], 1 / faces.length]); 10262 } 10263 return _path(s); 10264 }, 10265 _rectangle = function () { 10266 return _shape([ 10267 [ 0, 0, 1, 0 ], 10268 [ 1, 0, 1, 1 ], 10269 [ 1, 1, 0, 1 ], 10270 [ 0, 1, 0, 0 ] 10271 ]); 10272 }; 10273 10274 var _shapes = { 10275 "Circle": _circle, 10276 "Ellipse": _circle, 10277 "Diamond": function () { 10278 return _shape([ 10279 [ 0.5, 0, 1, 0.5 ], 10280 [ 1, 0.5, 0.5, 1 ], 10281 [ 0.5, 1, 0, 0.5 ], 10282 [ 0, 0.5, 0.5, 0 ] 10283 ]); 10284 }, 10285 "Rectangle": _rectangle, 10286 "Square": _rectangle, 10287 "Triangle": function () { 10288 return _shape([ 10289 [ 0.5, 0, 1, 1 ], 10290 [ 1, 1, 0, 1 ], 10291 [ 0, 1, 0.5, 0] 10292 ]); 10293 }, 10294 "Path": function (params) { 10295 var points = params.points, p = [], tl = 0; 10296 for (var i = 0; i < points.length - 1; i++) { 10297 var l = Math.sqrt(Math.pow(points[i][2] - points[i][0]) + Math.pow(points[i][3] - points[i][1])); 10298 tl += l; 10299 p.push([points[i][0], points[i][1], points[i + 1][0], points[i + 1][1], l]); 10300 } 10301 for (var j = 0; j < p.length; j++) { 10302 p[j][4] = p[j][4] / tl; 10303 } 10304 return _path(p); 10305 } 10306 }, 10307 _rotate = function (points, amountInDegrees) { 10308 var o = [], theta = amountInDegrees / 180 * Math.PI; 10309 for (var i = 0; i < points.length; i++) { 10310 var _x = points[i][0] - 0.5, 10311 _y = points[i][1] - 0.5; 10312 10313 o.push([ 10314 0.5 + ((_x * Math.cos(theta)) - (_y * Math.sin(theta))), 10315 0.5 + ((_x * Math.sin(theta)) + (_y * Math.cos(theta))), 10316 points[i][2], 10317 points[i][3] 10318 ]); 10319 } 10320 return o; 10321 }; 10322 10323 if (!_shapes[shape]) { 10324 throw new Error("Shape [" + shape + "] is unknown by Perimeter Anchor type"); 10325 } 10326 10327 var da = _shapes[shape](params); 10328 if (params.rotation) { 10329 da = _rotate(da, params.rotation); 10330 } 10331 var a = params.jsPlumbInstance.makeDynamicAnchor(da); 10332 a.type = "Perimeter"; 10333 return a; 10334 }; 10335 }).call(typeof window !== 'undefined' ? window : this); 10336 /* 10337 * jsPlumb Community Edition 10338 * 10339 * Provides a way to visually connect elements on an HTML page, using SVG. 10340 * 10341 * This file contains the default Connectors, Endpoint and Overlay definitions. 10342 * 10343 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 10344 * 10345 * https://jsplumbtoolkit.com 10346 * https://github.com/jsplumb/jsplumb 10347 * 10348 * Dual licensed under the MIT and GPL2 licenses. 10349 */ 10350 ; 10351 (function () { 10352 10353 "use strict"; 10354 var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil, _jg = root.Biltong; 10355 10356 _jp.Segments = { 10357 10358 /* 10359 * Class: AbstractSegment 10360 * A Connector is made up of 1..N Segments, each of which has a Type, such as 'Straight', 'Arc', 10361 * 'Bezier'. This is new from 1.4.2, and gives us a lot more flexibility when drawing connections: things such 10362 * as rounded corners for flowchart connectors, for example, or a straight line stub for Bezier connections, are 10363 * much easier to do now. 10364 * 10365 * A Segment is responsible for providing coordinates for painting it, and also must be able to report its length. 10366 * 10367 */ 10368 AbstractSegment: function (params) { 10369 this.params = params; 10370 10371 /** 10372 * Function: findClosestPointOnPath 10373 * Finds the closest point on this segment to the given [x, y], 10374 * returning both the x and y of the point plus its distance from 10375 * the supplied point, and its location along the length of the 10376 * path inscribed by the segment. This implementation returns 10377 * Infinity for distance and null values for everything else; 10378 * subclasses are expected to override. 10379 */ 10380 this.findClosestPointOnPath = function (x, y) { 10381 return { 10382 d: Infinity, 10383 x: null, 10384 y: null, 10385 l: null 10386 }; 10387 }; 10388 10389 this.getBounds = function () { 10390 return { 10391 minX: Math.min(params.x1, params.x2), 10392 minY: Math.min(params.y1, params.y2), 10393 maxX: Math.max(params.x1, params.x2), 10394 maxY: Math.max(params.y1, params.y2) 10395 }; 10396 }; 10397 }, 10398 Straight: function (params) { 10399 var _super = _jp.Segments.AbstractSegment.apply(this, arguments), 10400 length, m, m2, x1, x2, y1, y2, 10401 _recalc = function () { 10402 length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); 10403 m = _jg.gradient({x: x1, y: y1}, {x: x2, y: y2}); 10404 m2 = -1 / m; 10405 }; 10406 10407 this.type = "Straight"; 10408 10409 this.getLength = function () { 10410 return length; 10411 }; 10412 this.getGradient = function () { 10413 return m; 10414 }; 10415 10416 this.getCoordinates = function () { 10417 return { x1: x1, y1: y1, x2: x2, y2: y2 }; 10418 }; 10419 this.setCoordinates = function (coords) { 10420 x1 = coords.x1; 10421 y1 = coords.y1; 10422 x2 = coords.x2; 10423 y2 = coords.y2; 10424 _recalc(); 10425 }; 10426 this.setCoordinates({x1: params.x1, y1: params.y1, x2: params.x2, y2: params.y2}); 10427 10428 this.getBounds = function () { 10429 return { 10430 minX: Math.min(x1, x2), 10431 minY: Math.min(y1, y2), 10432 maxX: Math.max(x1, x2), 10433 maxY: Math.max(y1, y2) 10434 }; 10435 }; 10436 10437 /** 10438 * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from 10439 * 0 to 1 inclusive. for the straight line segment this is simple maths. 10440 */ 10441 this.pointOnPath = function (location, absolute) { 10442 if (location === 0 && !absolute) { 10443 return { x: x1, y: y1 }; 10444 } 10445 else if (location === 1 && !absolute) { 10446 return { x: x2, y: y2 }; 10447 } 10448 else { 10449 var l = absolute ? location > 0 ? location : length + location : location * length; 10450 return _jg.pointOnLine({x: x1, y: y1}, {x: x2, y: y2}, l); 10451 } 10452 }; 10453 10454 /** 10455 * returns the gradient of the segment at the given point - which for us is constant. 10456 */ 10457 this.gradientAtPoint = function (_) { 10458 return m; 10459 }; 10460 10461 /** 10462 * returns the point on the segment's path that is 'distance' along the length of the path from 'location', where 10463 * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. 10464 * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance. 10465 */ 10466 this.pointAlongPathFrom = function (location, distance, absolute) { 10467 var p = this.pointOnPath(location, absolute), 10468 farAwayPoint = distance <= 0 ? {x: x1, y: y1} : {x: x2, y: y2 }; 10469 10470 /* 10471 location == 1 ? { 10472 x:x1 + ((x2 - x1) * 10), 10473 y:y1 + ((y1 - y2) * 10) 10474 } : 10475 */ 10476 10477 if (distance <= 0 && Math.abs(distance) > 1) { 10478 distance *= -1; 10479 } 10480 10481 return _jg.pointOnLine(p, farAwayPoint, distance); 10482 }; 10483 10484 // is c between a and b? 10485 var within = function (a, b, c) { 10486 return c >= Math.min(a, b) && c <= Math.max(a, b); 10487 }; 10488 // find which of a and b is closest to c 10489 var closest = function (a, b, c) { 10490 return Math.abs(c - a) < Math.abs(c - b) ? a : b; 10491 }; 10492 10493 /** 10494 Function: findClosestPointOnPath 10495 Finds the closest point on this segment to [x,y]. See 10496 notes on this method in AbstractSegment. 10497 */ 10498 this.findClosestPointOnPath = function (x, y) { 10499 var out = { 10500 d: Infinity, 10501 x: null, 10502 y: null, 10503 l: null, 10504 x1: x1, 10505 x2: x2, 10506 y1: y1, 10507 y2: y2 10508 }; 10509 10510 if (m === 0) { 10511 out.y = y1; 10512 out.x = within(x1, x2, x) ? x : closest(x1, x2, x); 10513 } 10514 else if (m === Infinity || m === -Infinity) { 10515 out.x = x1; 10516 out.y = within(y1, y2, y) ? y : closest(y1, y2, y); 10517 } 10518 else { 10519 // closest point lies on normal from given point to this line. 10520 var b = y1 - (m * x1), 10521 b2 = y - (m2 * x), 10522 // y1 = m.x1 + b and y1 = m2.x1 + b2 10523 // so m.x1 + b = m2.x1 + b2 10524 // x1(m - m2) = b2 - b 10525 // x1 = (b2 - b) / (m - m2) 10526 _x1 = (b2 - b) / (m - m2), 10527 _y1 = (m * _x1) + b; 10528 10529 out.x = within(x1, x2, _x1) ? _x1 : closest(x1, x2, _x1);//_x1; 10530 out.y = within(y1, y2, _y1) ? _y1 : closest(y1, y2, _y1);//_y1; 10531 } 10532 10533 var fractionInSegment = _jg.lineLength([ out.x, out.y ], [ x1, y1 ]); 10534 out.d = _jg.lineLength([x, y], [out.x, out.y]); 10535 out.l = fractionInSegment / length; 10536 return out; 10537 }; 10538 }, 10539 10540 /* 10541 Arc Segment. You need to supply: 10542 10543 r - radius 10544 cx - center x for the arc 10545 cy - center y for the arc 10546 ac - whether the arc is anticlockwise or not. default is clockwise. 10547 10548 and then either: 10549 10550 startAngle - startAngle for the arc. 10551 endAngle - endAngle for the arc. 10552 10553 or: 10554 10555 x1 - x for start point 10556 y1 - y for start point 10557 x2 - x for end point 10558 y2 - y for end point 10559 10560 */ 10561 Arc: function (params) { 10562 var _super = _jp.Segments.AbstractSegment.apply(this, arguments), 10563 _calcAngle = function (_x, _y) { 10564 return _jg.theta([params.cx, params.cy], [_x, _y]); 10565 }, 10566 _calcAngleForLocation = function (segment, location) { 10567 if (segment.anticlockwise) { 10568 var sa = segment.startAngle < segment.endAngle ? segment.startAngle + TWO_PI : segment.startAngle, 10569 s = Math.abs(sa - segment.endAngle); 10570 return sa - (s * location); 10571 } 10572 else { 10573 var ea = segment.endAngle < segment.startAngle ? segment.endAngle + TWO_PI : segment.endAngle, 10574 ss = Math.abs(ea - segment.startAngle); 10575 10576 return segment.startAngle + (ss * location); 10577 } 10578 }, 10579 TWO_PI = 2 * Math.PI; 10580 10581 this.radius = params.r; 10582 this.anticlockwise = params.ac; 10583 this.type = "Arc"; 10584 10585 if (params.startAngle && params.endAngle) { 10586 this.startAngle = params.startAngle; 10587 this.endAngle = params.endAngle; 10588 this.x1 = params.cx + (this.radius * Math.cos(params.startAngle)); 10589 this.y1 = params.cy + (this.radius * Math.sin(params.startAngle)); 10590 this.x2 = params.cx + (this.radius * Math.cos(params.endAngle)); 10591 this.y2 = params.cy + (this.radius * Math.sin(params.endAngle)); 10592 } 10593 else { 10594 this.startAngle = _calcAngle(params.x1, params.y1); 10595 this.endAngle = _calcAngle(params.x2, params.y2); 10596 this.x1 = params.x1; 10597 this.y1 = params.y1; 10598 this.x2 = params.x2; 10599 this.y2 = params.y2; 10600 } 10601 10602 if (this.endAngle < 0) { 10603 this.endAngle += TWO_PI; 10604 } 10605 if (this.startAngle < 0) { 10606 this.startAngle += TWO_PI; 10607 } 10608 10609 // segment is used by vml 10610 //this.segment = _jg.quadrant([this.x1, this.y1], [this.x2, this.y2]); 10611 10612 // we now have startAngle and endAngle as positive numbers, meaning the 10613 // absolute difference (|d|) between them is the sweep (s) of this arc, unless the 10614 // arc is 'anticlockwise' in which case 's' is given by 2PI - |d|. 10615 10616 var ea = this.endAngle < this.startAngle ? this.endAngle + TWO_PI : this.endAngle; 10617 this.sweep = Math.abs(ea - this.startAngle); 10618 if (this.anticlockwise) { 10619 this.sweep = TWO_PI - this.sweep; 10620 } 10621 var circumference = 2 * Math.PI * this.radius, 10622 frac = this.sweep / TWO_PI, 10623 length = circumference * frac; 10624 10625 this.getLength = function () { 10626 return length; 10627 }; 10628 10629 this.getBounds = function () { 10630 return { 10631 minX: params.cx - params.r, 10632 maxX: params.cx + params.r, 10633 minY: params.cy - params.r, 10634 maxY: params.cy + params.r 10635 }; 10636 }; 10637 10638 var VERY_SMALL_VALUE = 0.0000000001, 10639 gentleRound = function (n) { 10640 var f = Math.floor(n), r = Math.ceil(n); 10641 if (n - f < VERY_SMALL_VALUE) { 10642 return f; 10643 } 10644 else if (r - n < VERY_SMALL_VALUE) { 10645 return r; 10646 } 10647 return n; 10648 }; 10649 10650 /** 10651 * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from 10652 * 0 to 1 inclusive. 10653 */ 10654 this.pointOnPath = function (location, absolute) { 10655 10656 if (location === 0) { 10657 return { x: this.x1, y: this.y1, theta: this.startAngle }; 10658 } 10659 else if (location === 1) { 10660 return { x: this.x2, y: this.y2, theta: this.endAngle }; 10661 } 10662 10663 if (absolute) { 10664 location = location / length; 10665 } 10666 10667 var angle = _calcAngleForLocation(this, location), 10668 _x = params.cx + (params.r * Math.cos(angle)), 10669 _y = params.cy + (params.r * Math.sin(angle)); 10670 10671 return { x: gentleRound(_x), y: gentleRound(_y), theta: angle }; 10672 }; 10673 10674 /** 10675 * returns the gradient of the segment at the given point. 10676 */ 10677 this.gradientAtPoint = function (location, absolute) { 10678 var p = this.pointOnPath(location, absolute); 10679 var m = _jg.normal([ params.cx, params.cy ], [p.x, p.y ]); 10680 if (!this.anticlockwise && (m === Infinity || m === -Infinity)) { 10681 m *= -1; 10682 } 10683 return m; 10684 }; 10685 10686 this.pointAlongPathFrom = function (location, distance, absolute) { 10687 var p = this.pointOnPath(location, absolute), 10688 arcSpan = distance / circumference * 2 * Math.PI, 10689 dir = this.anticlockwise ? -1 : 1, 10690 startAngle = p.theta + (dir * arcSpan), 10691 startX = params.cx + (this.radius * Math.cos(startAngle)), 10692 startY = params.cy + (this.radius * Math.sin(startAngle)); 10693 10694 return {x: startX, y: startY}; 10695 }; 10696 }, 10697 10698 Bezier: function (params) { 10699 this.curve = [ 10700 { x: params.x1, y: params.y1}, 10701 { x: params.cp1x, y: params.cp1y }, 10702 { x: params.cp2x, y: params.cp2y }, 10703 { x: params.x2, y: params.y2 } 10704 ]; 10705 10706 var _super = _jp.Segments.AbstractSegment.apply(this, arguments); 10707 // although this is not a strictly rigorous determination of bounds 10708 // of a bezier curve, it works for the types of curves that this segment 10709 // type produces. 10710 this.bounds = { 10711 minX: Math.min(params.x1, params.x2, params.cp1x, params.cp2x), 10712 minY: Math.min(params.y1, params.y2, params.cp1y, params.cp2y), 10713 maxX: Math.max(params.x1, params.x2, params.cp1x, params.cp2x), 10714 maxY: Math.max(params.y1, params.y2, params.cp1y, params.cp2y) 10715 }; 10716 10717 this.type = "Bezier"; 10718 10719 var _translateLocation = function (_curve, location, absolute) { 10720 if (absolute) { 10721 location = root.jsBezier.locationAlongCurveFrom(_curve, location > 0 ? 0 : 1, location); 10722 } 10723 10724 return location; 10725 }; 10726 10727 /** 10728 * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from 10729 * 0 to 1 inclusive. 10730 */ 10731 this.pointOnPath = function (location, absolute) { 10732 location = _translateLocation(this.curve, location, absolute); 10733 return root.jsBezier.pointOnCurve(this.curve, location); 10734 }; 10735 10736 /** 10737 * returns the gradient of the segment at the given point. 10738 */ 10739 this.gradientAtPoint = function (location, absolute) { 10740 location = _translateLocation(this.curve, location, absolute); 10741 return root.jsBezier.gradientAtPoint(this.curve, location); 10742 }; 10743 10744 this.pointAlongPathFrom = function (location, distance, absolute) { 10745 location = _translateLocation(this.curve, location, absolute); 10746 return root.jsBezier.pointAlongCurveFrom(this.curve, location, distance); 10747 }; 10748 10749 this.getLength = function () { 10750 return root.jsBezier.getLength(this.curve); 10751 }; 10752 10753 this.getBounds = function () { 10754 return this.bounds; 10755 }; 10756 } 10757 }; 10758 10759 _jp.SegmentRenderer = { 10760 getPath: function (segment) { 10761 return ({ 10762 "Straight": function () { 10763 var d = segment.getCoordinates(); 10764 return "M " + d.x1 + " " + d.y1 + " L " + d.x2 + " " + d.y2; 10765 }, 10766 "Bezier": function () { 10767 var d = segment.params; 10768 return "M " + d.x1 + " " + d.y1 + 10769 " C " + d.cp1x + " " + d.cp1y + " " + d.cp2x + " " + d.cp2y + " " + d.x2 + " " + d.y2; 10770 }, 10771 "Arc": function () { 10772 var d = segment.params, 10773 laf = segment.sweep > Math.PI ? 1 : 0, 10774 sf = segment.anticlockwise ? 0 : 1; 10775 10776 return "M" + segment.x1 + " " + segment.y1 + " A " + segment.radius + " " + d.r + " 0 " + laf + "," + sf + " " + segment.x2 + " " + segment.y2; 10777 } 10778 })[segment.type](); 10779 } 10780 }; 10781 10782 /* 10783 Class: AbstractComponent 10784 Superclass for AbstractConnector and AbstractEndpoint. 10785 */ 10786 var AbstractComponent = function () { 10787 this.resetBounds = function () { 10788 this.bounds = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity }; 10789 }; 10790 this.resetBounds(); 10791 }; 10792 10793 /* 10794 * Class: AbstractConnector 10795 * Superclass for all Connectors; here is where Segments are managed. This is exposed on jsPlumb just so it 10796 * can be accessed from other files. You should not try to instantiate one of these directly. 10797 * 10798 * When this class is asked for a pointOnPath, or gradient etc, it must first figure out which segment to dispatch 10799 * that request to. This is done by keeping track of the total connector length as segments are added, and also 10800 * their cumulative ratios to the total length. Then when the right segment is found it is a simple case of dispatching 10801 * the request to it (and adjusting 'location' so that it is relative to the beginning of that segment.) 10802 */ 10803 _jp.Connectors.AbstractConnector = function (params) { 10804 10805 AbstractComponent.apply(this, arguments); 10806 10807 var segments = [], 10808 totalLength = 0, 10809 segmentProportions = [], 10810 segmentProportionalLengths = [], 10811 stub = params.stub || 0, 10812 sourceStub = _ju.isArray(stub) ? stub[0] : stub, 10813 targetStub = _ju.isArray(stub) ? stub[1] : stub, 10814 gap = params.gap || 0, 10815 sourceGap = _ju.isArray(gap) ? gap[0] : gap, 10816 targetGap = _ju.isArray(gap) ? gap[1] : gap, 10817 userProvidedSegments = null, 10818 edited = false, 10819 paintInfo = null, 10820 geometry = null, 10821 editable = params.editable !== false && _jp.ConnectorEditors != null && _jp.ConnectorEditors[this.type] != null; 10822 10823 var _setGeometry = this.setGeometry = function(g, internallyComputed) { 10824 edited = (!internallyComputed); 10825 geometry = g; 10826 }; 10827 var _getGeometry = this.getGeometry = function() { 10828 return geometry; 10829 }; 10830 10831 this.getPathData = function() { 10832 var p = ""; 10833 for (var i = 0; i < segments.length; i++) { 10834 p += _jp.SegmentRenderer.getPath(segments[i]); 10835 p += " "; 10836 } 10837 return p; 10838 }; 10839 10840 this.hasBeenEdited = function() { return edited; }; 10841 this.isEditing = function() { return this.editor != null && this.editor.isActive(); }; 10842 this.setEditable = function(e) { 10843 // if this connector has an editor already, or 10844 // if an editor for this connector's type is available, or 10845 // if the child declares an overrideSetEditable and it does not return false, editable is true. 10846 if (e && _jp.ConnectorEditors != null && _jp.ConnectorEditors[this.type] != null && (this.overrideSetEditable == null || this.overrideSetEditable())) { 10847 editable = e; 10848 } else { 10849 editable = false; 10850 } 10851 return editable; 10852 }; 10853 this.isEditable = function() { return editable; }; 10854 10855 /** 10856 * Function: findSegmentForPoint 10857 * Returns the segment that is closest to the given [x,y], 10858 * null if nothing found. This function returns a JS 10859 * object with: 10860 * 10861 * d - distance from segment 10862 * l - proportional location in segment 10863 * x - x point on the segment 10864 * y - y point on the segment 10865 * s - the segment itself. 10866 */ 10867 this.findSegmentForPoint = function (x, y) { 10868 var out = { d: Infinity, s: null, x: null, y: null, l: null }; 10869 for (var i = 0; i < segments.length; i++) { 10870 var _s = segments[i].findClosestPointOnPath(x, y); 10871 if (_s.d < out.d) { 10872 out.d = _s.d; 10873 out.l = _s.l; 10874 out.x = _s.x; 10875 out.y = _s.y; 10876 out.s = segments[i]; 10877 out.x1 = _s.x1; 10878 out.x2 = _s.x2; 10879 out.y1 = _s.y1; 10880 out.y2 = _s.y2; 10881 out.index = i; 10882 } 10883 } 10884 10885 return out; 10886 }; 10887 10888 var _updateSegmentProportions = function () { 10889 var curLoc = 0; 10890 for (var i = 0; i < segments.length; i++) { 10891 var sl = segments[i].getLength(); 10892 segmentProportionalLengths[i] = sl / totalLength; 10893 segmentProportions[i] = [curLoc, (curLoc += (sl / totalLength)) ]; 10894 } 10895 }, 10896 10897 /** 10898 * returns [segment, proportion of travel in segment, segment index] for the segment 10899 * that contains the point which is 'location' distance along the entire path, where 10900 * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths 10901 * are made up of a list of segments, each of which contributes some fraction to 10902 * the total length. 10903 * From 1.3.10 this also supports the 'absolute' property, which lets us specify a location 10904 * as the absolute distance in pixels, rather than a proportion of the total path. 10905 */ 10906 _findSegmentForLocation = function (location, absolute) { 10907 if (absolute) { 10908 location = location > 0 ? location / totalLength : (totalLength + location) / totalLength; 10909 } 10910 var idx = segmentProportions.length - 1, inSegmentProportion = 1; 10911 for (var i = 0; i < segmentProportions.length; i++) { 10912 if (segmentProportions[i][1] >= location) { 10913 idx = i; 10914 // todo is this correct for all connector path types? 10915 inSegmentProportion = location === 1 ? 1 : location === 0 ? 0 : (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; 10916 break; 10917 } 10918 } 10919 return { segment: segments[idx], proportion: inSegmentProportion, index: idx }; 10920 }, 10921 _addSegment = function (conn, type, params) { 10922 if (params.x1 === params.x2 && params.y1 === params.y2) { 10923 return; 10924 } 10925 var s = new _jp.Segments[type](params); 10926 segments.push(s); 10927 totalLength += s.getLength(); 10928 conn.updateBounds(s); 10929 }, 10930 _clearSegments = function () { 10931 totalLength = segments.length = segmentProportions.length = segmentProportionalLengths.length = 0; 10932 }; 10933 10934 this.setSegments = function (_segs) { 10935 userProvidedSegments = []; 10936 totalLength = 0; 10937 for (var i = 0; i < _segs.length; i++) { 10938 userProvidedSegments.push(_segs[i]); 10939 totalLength += _segs[i].getLength(); 10940 } 10941 }; 10942 10943 this.getLength = function() { 10944 return totalLength; 10945 }; 10946 10947 var _prepareCompute = function (params) { 10948 this.strokeWidth = params.strokeWidth; 10949 var segment = _jg.quadrant(params.sourcePos, params.targetPos), 10950 swapX = params.targetPos[0] < params.sourcePos[0], 10951 swapY = params.targetPos[1] < params.sourcePos[1], 10952 lw = params.strokeWidth || 1, 10953 so = params.sourceEndpoint.anchor.getOrientation(params.sourceEndpoint), 10954 to = params.targetEndpoint.anchor.getOrientation(params.targetEndpoint), 10955 x = swapX ? params.targetPos[0] : params.sourcePos[0], 10956 y = swapY ? params.targetPos[1] : params.sourcePos[1], 10957 w = Math.abs(params.targetPos[0] - params.sourcePos[0]), 10958 h = Math.abs(params.targetPos[1] - params.sourcePos[1]); 10959 10960 // if either anchor does not have an orientation set, we derive one from their relative 10961 // positions. we fix the axis to be the one in which the two elements are further apart, and 10962 // point each anchor at the other element. this is also used when dragging a new connection. 10963 if (so[0] === 0 && so[1] === 0 || to[0] === 0 && to[1] === 0) { 10964 var index = w > h ? 0 : 1, oIndex = [1, 0][index]; 10965 so = []; 10966 to = []; 10967 so[index] = params.sourcePos[index] > params.targetPos[index] ? -1 : 1; 10968 to[index] = params.sourcePos[index] > params.targetPos[index] ? 1 : -1; 10969 so[oIndex] = 0; 10970 to[oIndex] = 0; 10971 } 10972 10973 var sx = swapX ? w + (sourceGap * so[0]) : sourceGap * so[0], 10974 sy = swapY ? h + (sourceGap * so[1]) : sourceGap * so[1], 10975 tx = swapX ? targetGap * to[0] : w + (targetGap * to[0]), 10976 ty = swapY ? targetGap * to[1] : h + (targetGap * to[1]), 10977 oProduct = ((so[0] * to[0]) + (so[1] * to[1])); 10978 10979 var result = { 10980 sx: sx, sy: sy, tx: tx, ty: ty, lw: lw, 10981 xSpan: Math.abs(tx - sx), 10982 ySpan: Math.abs(ty - sy), 10983 mx: (sx + tx) / 2, 10984 my: (sy + ty) / 2, 10985 so: so, to: to, x: x, y: y, w: w, h: h, 10986 segment: segment, 10987 startStubX: sx + (so[0] * sourceStub), 10988 startStubY: sy + (so[1] * sourceStub), 10989 endStubX: tx + (to[0] * targetStub), 10990 endStubY: ty + (to[1] * targetStub), 10991 isXGreaterThanStubTimes2: Math.abs(sx - tx) > (sourceStub + targetStub), 10992 isYGreaterThanStubTimes2: Math.abs(sy - ty) > (sourceStub + targetStub), 10993 opposite: oProduct === -1, 10994 perpendicular: oProduct === 0, 10995 orthogonal: oProduct === 1, 10996 sourceAxis: so[0] === 0 ? "y" : "x", 10997 points: [x, y, w, h, sx, sy, tx, ty ] 10998 }; 10999 result.anchorOrientation = result.opposite ? "opposite" : result.orthogonal ? "orthogonal" : "perpendicular"; 11000 return result; 11001 }; 11002 11003 this.getSegments = function () { 11004 return segments; 11005 }; 11006 11007 this.updateBounds = function (segment) { 11008 var segBounds = segment.getBounds(); 11009 this.bounds.minX = Math.min(this.bounds.minX, segBounds.minX); 11010 this.bounds.maxX = Math.max(this.bounds.maxX, segBounds.maxX); 11011 this.bounds.minY = Math.min(this.bounds.minY, segBounds.minY); 11012 this.bounds.maxY = Math.max(this.bounds.maxY, segBounds.maxY); 11013 }; 11014 11015 var dumpSegmentsToConsole = function () { 11016 console.log("SEGMENTS:"); 11017 for (var i = 0; i < segments.length; i++) { 11018 console.log(segments[i].type, segments[i].getLength(), segmentProportions[i]); 11019 } 11020 }; 11021 11022 this.pointOnPath = function (location, absolute) { 11023 var seg = _findSegmentForLocation(location, absolute); 11024 return seg.segment && seg.segment.pointOnPath(seg.proportion, false) || [0, 0]; 11025 }; 11026 11027 this.gradientAtPoint = function (location, absolute) { 11028 var seg = _findSegmentForLocation(location, absolute); 11029 return seg.segment && seg.segment.gradientAtPoint(seg.proportion, false) || 0; 11030 }; 11031 11032 this.pointAlongPathFrom = function (location, distance, absolute) { 11033 var seg = _findSegmentForLocation(location, absolute); 11034 // TODO what happens if this crosses to the next segment? 11035 return seg.segment && seg.segment.pointAlongPathFrom(seg.proportion, distance, false) || [0, 0]; 11036 }; 11037 11038 this.compute = function (params) { 11039 paintInfo = _prepareCompute.call(this, params); 11040 11041 _clearSegments(); 11042 this._compute(paintInfo, params); 11043 this.x = paintInfo.points[0]; 11044 this.y = paintInfo.points[1]; 11045 this.w = paintInfo.points[2]; 11046 this.h = paintInfo.points[3]; 11047 this.segment = paintInfo.segment; 11048 _updateSegmentProportions(); 11049 }; 11050 11051 return { 11052 addSegment: _addSegment, 11053 prepareCompute: _prepareCompute, 11054 sourceStub: sourceStub, 11055 targetStub: targetStub, 11056 maxStub: Math.max(sourceStub, targetStub), 11057 sourceGap: sourceGap, 11058 targetGap: targetGap, 11059 maxGap: Math.max(sourceGap, targetGap), 11060 setGeometry:_setGeometry, 11061 getGeometry:_getGeometry 11062 }; 11063 }; 11064 _ju.extend(_jp.Connectors.AbstractConnector, AbstractComponent); 11065 11066 11067 // ********************************* END OF CONNECTOR TYPES ******************************************************************* 11068 11069 // ********************************* ENDPOINT TYPES ******************************************************************* 11070 11071 _jp.Endpoints.AbstractEndpoint = function (params) { 11072 AbstractComponent.apply(this, arguments); 11073 var compute = this.compute = function (anchorPoint, orientation, endpointStyle, connectorPaintStyle) { 11074 var out = this._compute.apply(this, arguments); 11075 this.x = out[0]; 11076 this.y = out[1]; 11077 this.w = out[2]; 11078 this.h = out[3]; 11079 this.bounds.minX = this.x; 11080 this.bounds.minY = this.y; 11081 this.bounds.maxX = this.x + this.w; 11082 this.bounds.maxY = this.y + this.h; 11083 return out; 11084 }; 11085 return { 11086 compute: compute, 11087 cssClass: params.cssClass 11088 }; 11089 }; 11090 _ju.extend(_jp.Endpoints.AbstractEndpoint, AbstractComponent); 11091 11092 /** 11093 * Class: Endpoints.Dot 11094 * A round endpoint, with default radius 10 pixels. 11095 */ 11096 11097 /** 11098 * Function: Constructor 11099 * 11100 * Parameters: 11101 * 11102 * radius - radius of the endpoint. defaults to 10 pixels. 11103 */ 11104 _jp.Endpoints.Dot = function (params) { 11105 this.type = "Dot"; 11106 var _super = _jp.Endpoints.AbstractEndpoint.apply(this, arguments); 11107 params = params || {}; 11108 this.radius = params.radius || 10; 11109 this.defaultOffset = 0.5 * this.radius; 11110 this.defaultInnerRadius = this.radius / 3; 11111 11112 this._compute = function (anchorPoint, orientation, endpointStyle, connectorPaintStyle) { 11113 this.radius = endpointStyle.radius || this.radius; 11114 var x = anchorPoint[0] - this.radius, 11115 y = anchorPoint[1] - this.radius, 11116 w = this.radius * 2, 11117 h = this.radius * 2; 11118 11119 if (endpointStyle.stroke) { 11120 var lw = endpointStyle.strokeWidth || 1; 11121 x -= lw; 11122 y -= lw; 11123 w += (lw * 2); 11124 h += (lw * 2); 11125 } 11126 return [ x, y, w, h, this.radius ]; 11127 }; 11128 }; 11129 _ju.extend(_jp.Endpoints.Dot, _jp.Endpoints.AbstractEndpoint); 11130 11131 _jp.Endpoints.Rectangle = function (params) { 11132 this.type = "Rectangle"; 11133 var _super = _jp.Endpoints.AbstractEndpoint.apply(this, arguments); 11134 params = params || {}; 11135 this.width = params.width || 20; 11136 this.height = params.height || 20; 11137 11138 this._compute = function (anchorPoint, orientation, endpointStyle, connectorPaintStyle) { 11139 var width = endpointStyle.width || this.width, 11140 height = endpointStyle.height || this.height, 11141 x = anchorPoint[0] - (width / 2), 11142 y = anchorPoint[1] - (height / 2); 11143 11144 return [ x, y, width, height]; 11145 }; 11146 }; 11147 _ju.extend(_jp.Endpoints.Rectangle, _jp.Endpoints.AbstractEndpoint); 11148 11149 var DOMElementEndpoint = function (params) { 11150 _jp.jsPlumbUIComponent.apply(this, arguments); 11151 this._jsPlumb.displayElements = []; 11152 }; 11153 _ju.extend(DOMElementEndpoint, _jp.jsPlumbUIComponent, { 11154 getDisplayElements: function () { 11155 return this._jsPlumb.displayElements; 11156 }, 11157 appendDisplayElement: function (el) { 11158 this._jsPlumb.displayElements.push(el); 11159 } 11160 }); 11161 11162 /** 11163 * Class: Endpoints.Image 11164 * Draws an image as the Endpoint. 11165 */ 11166 /** 11167 * Function: Constructor 11168 * 11169 * Parameters: 11170 * 11171 * src - location of the image to use. 11172 11173 TODO: multiple references to self. not sure quite how to get rid of them entirely. perhaps self = null in the cleanup 11174 function will suffice 11175 11176 TODO this class still might leak memory. 11177 11178 */ 11179 _jp.Endpoints.Image = function (params) { 11180 11181 this.type = "Image"; 11182 DOMElementEndpoint.apply(this, arguments); 11183 _jp.Endpoints.AbstractEndpoint.apply(this, arguments); 11184 11185 var _onload = params.onload, 11186 src = params.src || params.url, 11187 clazz = params.cssClass ? " " + params.cssClass : ""; 11188 11189 this._jsPlumb.img = new Image(); 11190 this._jsPlumb.ready = false; 11191 this._jsPlumb.initialized = false; 11192 this._jsPlumb.deleted = false; 11193 this._jsPlumb.widthToUse = params.width; 11194 this._jsPlumb.heightToUse = params.height; 11195 this._jsPlumb.endpoint = params.endpoint; 11196 11197 this._jsPlumb.img.onload = function () { 11198 if (this._jsPlumb != null) { 11199 this._jsPlumb.ready = true; 11200 this._jsPlumb.widthToUse = this._jsPlumb.widthToUse || this._jsPlumb.img.width; 11201 this._jsPlumb.heightToUse = this._jsPlumb.heightToUse || this._jsPlumb.img.height; 11202 if (_onload) { 11203 _onload(this); 11204 } 11205 } 11206 }.bind(this); 11207 11208 /* 11209 Function: setImage 11210 Sets the Image to use in this Endpoint. 11211 11212 Parameters: 11213 img - may be a URL or an Image object 11214 onload - optional; a callback to execute once the image has loaded. 11215 */ 11216 this._jsPlumb.endpoint.setImage = function (_img, onload) { 11217 var s = _img.constructor === String ? _img : _img.src; 11218 _onload = onload; 11219 this._jsPlumb.img.src = s; 11220 11221 if (this.canvas != null) { 11222 this.canvas.setAttribute("src", this._jsPlumb.img.src); 11223 } 11224 }.bind(this); 11225 11226 this._jsPlumb.endpoint.setImage(src, _onload); 11227 this._compute = function (anchorPoint, orientation, endpointStyle, connectorPaintStyle) { 11228 this.anchorPoint = anchorPoint; 11229 if (this._jsPlumb.ready) { 11230 return [anchorPoint[0] - this._jsPlumb.widthToUse / 2, anchorPoint[1] - this._jsPlumb.heightToUse / 2, 11231 this._jsPlumb.widthToUse, this._jsPlumb.heightToUse]; 11232 } 11233 else { 11234 return [0, 0, 0, 0]; 11235 } 11236 }; 11237 11238 this.canvas = _jp.createElement("img", { 11239 position:"absolute", 11240 margin:0, 11241 padding:0, 11242 outline:0 11243 }, this._jsPlumb.instance.endpointClass + clazz); 11244 11245 if (this._jsPlumb.widthToUse) { 11246 this.canvas.setAttribute("width", this._jsPlumb.widthToUse); 11247 } 11248 if (this._jsPlumb.heightToUse) { 11249 this.canvas.setAttribute("height", this._jsPlumb.heightToUse); 11250 } 11251 this._jsPlumb.instance.appendElement(this.canvas); 11252 11253 this.actuallyPaint = function (d, style, anchor) { 11254 if (!this._jsPlumb.deleted) { 11255 if (!this._jsPlumb.initialized) { 11256 this.canvas.setAttribute("src", this._jsPlumb.img.src); 11257 this.appendDisplayElement(this.canvas); 11258 this._jsPlumb.initialized = true; 11259 } 11260 var x = this.anchorPoint[0] - (this._jsPlumb.widthToUse / 2), 11261 y = this.anchorPoint[1] - (this._jsPlumb.heightToUse / 2); 11262 _ju.sizeElement(this.canvas, x, y, this._jsPlumb.widthToUse, this._jsPlumb.heightToUse); 11263 } 11264 }; 11265 11266 this.paint = function (style, anchor) { 11267 if (this._jsPlumb != null) { // may have been deleted 11268 if (this._jsPlumb.ready) { 11269 this.actuallyPaint(style, anchor); 11270 } 11271 else { 11272 root.setTimeout(function () { 11273 this.paint(style, anchor); 11274 }.bind(this), 200); 11275 } 11276 } 11277 }; 11278 }; 11279 _ju.extend(_jp.Endpoints.Image, [ DOMElementEndpoint, _jp.Endpoints.AbstractEndpoint ], { 11280 cleanup: function (force) { 11281 if (force) { 11282 this._jsPlumb.deleted = true; 11283 if (this.canvas) { 11284 this.canvas.parentNode.removeChild(this.canvas); 11285 } 11286 this.canvas = null; 11287 } 11288 } 11289 }); 11290 11291 /* 11292 * Class: Endpoints.Blank 11293 * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. 11294 */ 11295 _jp.Endpoints.Blank = function (params) { 11296 var _super = _jp.Endpoints.AbstractEndpoint.apply(this, arguments); 11297 this.type = "Blank"; 11298 DOMElementEndpoint.apply(this, arguments); 11299 this._compute = function (anchorPoint, orientation, endpointStyle, connectorPaintStyle) { 11300 return [anchorPoint[0], anchorPoint[1], 10, 0]; 11301 }; 11302 11303 var clazz = params.cssClass ? " " + params.cssClass : ""; 11304 11305 this.canvas = _jp.createElement("div", { 11306 display: "block", 11307 width: "1px", 11308 height: "1px", 11309 background: "transparent", 11310 position: "absolute" 11311 }, this._jsPlumb.instance.endpointClass + clazz); 11312 11313 this._jsPlumb.instance.appendElement(this.canvas); 11314 11315 this.paint = function (style, anchor) { 11316 _ju.sizeElement(this.canvas, this.x, this.y, this.w, this.h); 11317 }; 11318 }; 11319 _ju.extend(_jp.Endpoints.Blank, [_jp.Endpoints.AbstractEndpoint, DOMElementEndpoint], { 11320 cleanup: function () { 11321 if (this.canvas && this.canvas.parentNode) { 11322 this.canvas.parentNode.removeChild(this.canvas); 11323 } 11324 } 11325 }); 11326 11327 /* 11328 * Class: Endpoints.Triangle 11329 * A triangular Endpoint. 11330 */ 11331 /* 11332 * Function: Constructor 11333 * 11334 * Parameters: 11335 * 11336 * width width of the triangle's base. defaults to 55 pixels. 11337 * height height of the triangle from base to apex. defaults to 55 pixels. 11338 */ 11339 _jp.Endpoints.Triangle = function (params) { 11340 this.type = "Triangle"; 11341 _jp.Endpoints.AbstractEndpoint.apply(this, arguments); 11342 var self = this; 11343 params = params || { }; 11344 params.width = params.width || 55; 11345 params.height = params.height || 55; 11346 this.width = params.width; 11347 this.height = params.height; 11348 this._compute = function (anchorPoint, orientation, endpointStyle, connectorPaintStyle) { 11349 var width = endpointStyle.width || self.width, 11350 height = endpointStyle.height || self.height, 11351 x = anchorPoint[0] - (width / 2), 11352 y = anchorPoint[1] - (height / 2); 11353 return [ x, y, width, height ]; 11354 }; 11355 }; 11356 // ********************************* END OF ENDPOINT TYPES ******************************************************************* 11357 11358 11359 // ********************************* OVERLAY DEFINITIONS *********************************************************************** 11360 11361 var AbstractOverlay = _jp.Overlays.AbstractOverlay = function (params) { 11362 this.visible = true; 11363 this.isAppendedAtTopLevel = true; 11364 this.component = params.component; 11365 this.loc = params.location == null ? 0.5 : params.location; 11366 this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; 11367 this.visible = params.visible !== false; 11368 }; 11369 AbstractOverlay.prototype = { 11370 cleanup: function (force) { 11371 if (force) { 11372 this.component = null; 11373 this.canvas = null; 11374 this.endpointLoc = null; 11375 } 11376 }, 11377 reattach:function(instance) { 11378 11379 }, 11380 setVisible: function (val) { 11381 this.visible = val; 11382 this.component.repaint(); 11383 }, 11384 isVisible: function () { 11385 return this.visible; 11386 }, 11387 hide: function () { 11388 this.setVisible(false); 11389 }, 11390 show: function () { 11391 this.setVisible(true); 11392 }, 11393 incrementLocation: function (amount) { 11394 this.loc += amount; 11395 this.component.repaint(); 11396 }, 11397 setLocation: function (l) { 11398 this.loc = l; 11399 this.component.repaint(); 11400 }, 11401 getLocation: function () { 11402 return this.loc; 11403 }, 11404 updateFrom:function() { } 11405 }; 11406 11407 11408 /* 11409 * Class: Overlays.Arrow 11410 * 11411 * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length 11412 * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction 11413 * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line 11414 * across the tail. 11415 */ 11416 /* 11417 * @constructor 11418 * 11419 * @param {Object} params Constructor params. 11420 * @param {Number} [params.length] Distance in pixels from head to tail baseline. default 20. 11421 * @param {Number} [params.width] Width in pixels of the tail baseline. default 20. 11422 * @param {String} [params.fill] Style to use when filling the arrow. defaults to "black". 11423 * @param {String} [params.stroke] Style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. 11424 * @param {Number} [params.stroke-width] Line width to use when stroking the arrow. defaults to 1, but only used if stroke is not null. 11425 * @param {Number} [params.foldback] Distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. 11426 * @param {Number} [params.location] Distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. 11427 * @param {NUmber} [params.direction] Indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. 11428 */ 11429 _jp.Overlays.Arrow = function (params) { 11430 this.type = "Arrow"; 11431 AbstractOverlay.apply(this, arguments); 11432 this.isAppendedAtTopLevel = false; 11433 params = params || {}; 11434 var self = this; 11435 11436 this.length = params.length || 20; 11437 this.width = params.width || 20; 11438 this.id = params.id; 11439 var direction = (params.direction || 1) < 0 ? -1 : 1, 11440 paintStyle = params.paintStyle || { "stroke-width": 1 }, 11441 // how far along the arrow the lines folding back in come to. default is 62.3%. 11442 foldback = params.foldback || 0.623; 11443 11444 this.computeMaxSize = function () { 11445 return self.width * 1.5; 11446 }; 11447 11448 this.elementCreated = function(p, component) { 11449 this.path = p; 11450 if (params.events) { 11451 for (var i in params.events) { 11452 _jp.on(p, i, params.events[i]); 11453 } 11454 } 11455 }; 11456 11457 this.draw = function (component, currentConnectionPaintStyle) { 11458 11459 var hxy, mid, txy, tail, cxy; 11460 if (component.pointAlongPathFrom) { 11461 11462 if (_ju.isString(this.loc) || this.loc > 1 || this.loc < 0) { 11463 var l = parseInt(this.loc, 10), 11464 fromLoc = this.loc < 0 ? 1 : 0; 11465 hxy = component.pointAlongPathFrom(fromLoc, l, false); 11466 mid = component.pointAlongPathFrom(fromLoc, l - (direction * this.length / 2), false); 11467 txy = _jg.pointOnLine(hxy, mid, this.length); 11468 } 11469 else if (this.loc === 1) { 11470 hxy = component.pointOnPath(this.loc); 11471 mid = component.pointAlongPathFrom(this.loc, -(this.length)); 11472 txy = _jg.pointOnLine(hxy, mid, this.length); 11473 11474 if (direction === -1) { 11475 var _ = txy; 11476 txy = hxy; 11477 hxy = _; 11478 } 11479 } 11480 else if (this.loc === 0) { 11481 txy = component.pointOnPath(this.loc); 11482 mid = component.pointAlongPathFrom(this.loc, this.length); 11483 hxy = _jg.pointOnLine(txy, mid, this.length); 11484 if (direction === -1) { 11485 var __ = txy; 11486 txy = hxy; 11487 hxy = __; 11488 } 11489 } 11490 else { 11491 hxy = component.pointAlongPathFrom(this.loc, direction * this.length / 2); 11492 mid = component.pointOnPath(this.loc); 11493 txy = _jg.pointOnLine(hxy, mid, this.length); 11494 } 11495 11496 tail = _jg.perpendicularLineTo(hxy, txy, this.width); 11497 cxy = _jg.pointOnLine(hxy, txy, foldback * this.length); 11498 11499 var d = { hxy: hxy, tail: tail, cxy: cxy }, 11500 stroke = paintStyle.stroke || currentConnectionPaintStyle.stroke, 11501 fill = paintStyle.fill || currentConnectionPaintStyle.stroke, 11502 lineWidth = paintStyle.strokeWidth || currentConnectionPaintStyle.strokeWidth; 11503 11504 return { 11505 component: component, 11506 d: d, 11507 "stroke-width": lineWidth, 11508 stroke: stroke, 11509 fill: fill, 11510 minX: Math.min(hxy.x, tail[0].x, tail[1].x), 11511 maxX: Math.max(hxy.x, tail[0].x, tail[1].x), 11512 minY: Math.min(hxy.y, tail[0].y, tail[1].y), 11513 maxY: Math.max(hxy.y, tail[0].y, tail[1].y) 11514 }; 11515 } 11516 else { 11517 return {component: component, minX: 0, maxX: 0, minY: 0, maxY: 0}; 11518 } 11519 }; 11520 }; 11521 _ju.extend(_jp.Overlays.Arrow, AbstractOverlay, { 11522 updateFrom:function(d) { 11523 this.length = d.length || this.length; 11524 this.width = d.width|| this.width; 11525 this.direction = d.direction != null ? d.direction : this.direction; 11526 this.foldback = d.foldback|| this.foldback; 11527 } 11528 }); 11529 11530 /* 11531 * Class: Overlays.PlainArrow 11532 * 11533 * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some 11534 * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does 11535 * a 'call' to Arrow with foldback set appropriately. 11536 */ 11537 /* 11538 * Function: Constructor 11539 * See <Overlays.Arrow> for allowed parameters for this overlay. 11540 */ 11541 _jp.Overlays.PlainArrow = function (params) { 11542 params = params || {}; 11543 var p = _jp.extend(params, {foldback: 1}); 11544 _jp.Overlays.Arrow.call(this, p); 11545 this.type = "PlainArrow"; 11546 }; 11547 _ju.extend(_jp.Overlays.PlainArrow, _jp.Overlays.Arrow); 11548 11549 /* 11550 * Class: Overlays.Diamond 11551 * 11552 * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just 11553 * happens that in this case, that point is greater than the length of the the arrow. 11554 * 11555 * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the 11556 * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of 11557 * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value 11558 * would be -l/4 in this case - move along one quarter of the total length. 11559 */ 11560 /* 11561 * Function: Constructor 11562 * See <Overlays.Arrow> for allowed parameters for this overlay. 11563 */ 11564 _jp.Overlays.Diamond = function (params) { 11565 params = params || {}; 11566 var l = params.length || 40, 11567 p = _jp.extend(params, {length: l / 2, foldback: 2}); 11568 _jp.Overlays.Arrow.call(this, p); 11569 this.type = "Diamond"; 11570 }; 11571 _ju.extend(_jp.Overlays.Diamond, _jp.Overlays.Arrow); 11572 11573 var _getDimensions = function (component, forceRefresh) { 11574 if (component._jsPlumb.cachedDimensions == null || forceRefresh) { 11575 component._jsPlumb.cachedDimensions = component.getDimensions(); 11576 } 11577 return component._jsPlumb.cachedDimensions; 11578 }; 11579 11580 // abstract superclass for overlays that add an element to the DOM. 11581 var AbstractDOMOverlay = function (params) { 11582 _jp.jsPlumbUIComponent.apply(this, arguments); 11583 AbstractOverlay.apply(this, arguments); 11584 11585 // hand off fired events to associated component. 11586 var _f = this.fire; 11587 this.fire = function () { 11588 _f.apply(this, arguments); 11589 if (this.component) { 11590 this.component.fire.apply(this.component, arguments); 11591 } 11592 }; 11593 11594 this.detached=false; 11595 this.id = params.id; 11596 this._jsPlumb.div = null; 11597 this._jsPlumb.initialised = false; 11598 this._jsPlumb.component = params.component; 11599 this._jsPlumb.cachedDimensions = null; 11600 this._jsPlumb.create = params.create; 11601 this._jsPlumb.initiallyInvisible = params.visible === false; 11602 11603 this.getElement = function () { 11604 if (this._jsPlumb.div == null) { 11605 var div = this._jsPlumb.div = _jp.getElement(this._jsPlumb.create(this._jsPlumb.component)); 11606 div.style.position = "absolute"; 11607 div.className = this._jsPlumb.instance.overlayClass + " " + 11608 (this.cssClass ? this.cssClass : 11609 params.cssClass ? params.cssClass : ""); 11610 this._jsPlumb.instance.appendElement(div); 11611 this._jsPlumb.instance.getId(div); 11612 this.canvas = div; 11613 11614 // in IE the top left corner is what it placed at the desired location. This will not 11615 // be fixed. IE8 is not going to be supported for much longer. 11616 var ts = "translate(-50%, -50%)"; 11617 div.style.webkitTransform = ts; 11618 div.style.mozTransform = ts; 11619 div.style.msTransform = ts; 11620 div.style.oTransform = ts; 11621 div.style.transform = ts; 11622 11623 // write the related component into the created element 11624 div._jsPlumb = this; 11625 11626 if (params.visible === false) { 11627 div.style.display = "none"; 11628 } 11629 } 11630 return this._jsPlumb.div; 11631 }; 11632 11633 this.draw = function (component, currentConnectionPaintStyle, absolutePosition) { 11634 var td = _getDimensions(this); 11635 if (td != null && td.length === 2) { 11636 var cxy = { x: 0, y: 0 }; 11637 11638 // absolutePosition would have been set by a call to connection.setAbsoluteOverlayPosition. 11639 if (absolutePosition) { 11640 cxy = { x: absolutePosition[0], y: absolutePosition[1] }; 11641 } 11642 else if (component.pointOnPath) { 11643 var loc = this.loc, absolute = false; 11644 if (_ju.isString(this.loc) || this.loc < 0 || this.loc > 1) { 11645 loc = parseInt(this.loc, 10); 11646 absolute = true; 11647 } 11648 cxy = component.pointOnPath(loc, absolute); // a connection 11649 } 11650 else { 11651 var locToUse = this.loc.constructor === Array ? this.loc : this.endpointLoc; 11652 cxy = { x: locToUse[0] * component.w, 11653 y: locToUse[1] * component.h }; 11654 } 11655 11656 var minx = cxy.x - (td[0] / 2), 11657 miny = cxy.y - (td[1] / 2); 11658 11659 return { 11660 component: component, 11661 d: { minx: minx, miny: miny, td: td, cxy: cxy }, 11662 minX: minx, 11663 maxX: minx + td[0], 11664 minY: miny, 11665 maxY: miny + td[1] 11666 }; 11667 } 11668 else { 11669 return {minX: 0, maxX: 0, minY: 0, maxY: 0}; 11670 } 11671 }; 11672 }; 11673 _ju.extend(AbstractDOMOverlay, [_jp.jsPlumbUIComponent, AbstractOverlay], { 11674 getDimensions: function () { 11675 return [1,1]; 11676 }, 11677 setVisible: function (state) { 11678 if (this._jsPlumb.div) { 11679 this._jsPlumb.div.style.display = state ? "block" : "none"; 11680 // if initially invisible, dimensions are 0,0 and never get updated 11681 if (state && this._jsPlumb.initiallyInvisible) { 11682 _getDimensions(this, true); 11683 this.component.repaint(); 11684 this._jsPlumb.initiallyInvisible = false; 11685 } 11686 } 11687 }, 11688 /* 11689 * Function: clearCachedDimensions 11690 * Clears the cached dimensions for the label. As a performance enhancement, label dimensions are 11691 * cached from 1.3.12 onwards. The cache is cleared when you change the label text, of course, but 11692 * there are other reasons why the text dimensions might change - if you make a change through CSS, for 11693 * example, you might change the font size. in that case you should explicitly call this method. 11694 */ 11695 clearCachedDimensions: function () { 11696 this._jsPlumb.cachedDimensions = null; 11697 }, 11698 cleanup: function (force) { 11699 if (force) { 11700 if (this._jsPlumb.div != null) { 11701 this._jsPlumb.div._jsPlumb = null; 11702 this._jsPlumb.instance.removeElement(this._jsPlumb.div); 11703 } 11704 } 11705 else { 11706 // if not a forced cleanup, just detach child from parent for now. 11707 if (this._jsPlumb && this._jsPlumb.div && this._jsPlumb.div.parentNode) { 11708 this._jsPlumb.div.parentNode.removeChild(this._jsPlumb.div); 11709 } 11710 this.detached = true; 11711 } 11712 11713 }, 11714 reattach:function(instance) { 11715 if (this._jsPlumb.div != null) { 11716 instance.getContainer().appendChild(this._jsPlumb.div); 11717 } 11718 this.detached = false; 11719 }, 11720 computeMaxSize: function () { 11721 var td = _getDimensions(this); 11722 return Math.max(td[0], td[1]); 11723 }, 11724 paint: function (p, containerExtents) { 11725 if (!this._jsPlumb.initialised) { 11726 this.getElement(); 11727 p.component.appendDisplayElement(this._jsPlumb.div); 11728 this._jsPlumb.initialised = true; 11729 if (this.detached) { 11730 this._jsPlumb.div.parentNode.removeChild(this._jsPlumb.div); 11731 } 11732 } 11733 this._jsPlumb.div.style.left = (p.component.x + p.d.minx) + "px"; 11734 this._jsPlumb.div.style.top = (p.component.y + p.d.miny) + "px"; 11735 } 11736 }); 11737 11738 /* 11739 * Class: Overlays.Custom 11740 * A Custom overlay. You supply a 'create' function which returns some DOM element, and jsPlumb positions it. 11741 * The 'create' function is passed a Connection or Endpoint. 11742 */ 11743 /* 11744 * Function: Constructor 11745 * 11746 * Parameters: 11747 * create - function for jsPlumb to call that returns a DOM element. 11748 * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. 11749 * id - optional id to use for later retrieval of this overlay. 11750 * 11751 */ 11752 _jp.Overlays.Custom = function (params) { 11753 this.type = "Custom"; 11754 AbstractDOMOverlay.apply(this, arguments); 11755 }; 11756 _ju.extend(_jp.Overlays.Custom, AbstractDOMOverlay); 11757 11758 _jp.Overlays.GuideLines = function () { 11759 var self = this; 11760 self.length = 50; 11761 self.strokeWidth = 5; 11762 this.type = "GuideLines"; 11763 AbstractOverlay.apply(this, arguments); 11764 _jp.jsPlumbUIComponent.apply(this, arguments); 11765 this.draw = function (connector, currentConnectionPaintStyle) { 11766 11767 var head = connector.pointAlongPathFrom(self.loc, self.length / 2), 11768 mid = connector.pointOnPath(self.loc), 11769 tail = _jg.pointOnLine(head, mid, self.length), 11770 tailLine = _jg.perpendicularLineTo(head, tail, 40), 11771 headLine = _jg.perpendicularLineTo(tail, head, 20); 11772 11773 return { 11774 connector: connector, 11775 head: head, 11776 tail: tail, 11777 headLine: headLine, 11778 tailLine: tailLine, 11779 minX: Math.min(head.x, tail.x, headLine[0].x, headLine[1].x), 11780 minY: Math.min(head.y, tail.y, headLine[0].y, headLine[1].y), 11781 maxX: Math.max(head.x, tail.x, headLine[0].x, headLine[1].x), 11782 maxY: Math.max(head.y, tail.y, headLine[0].y, headLine[1].y) 11783 }; 11784 }; 11785 11786 // this.cleanup = function() { }; // nothing to clean up for GuideLines 11787 }; 11788 11789 /* 11790 * Class: Overlays.Label 11791 11792 */ 11793 /* 11794 * Function: Constructor 11795 * 11796 * Parameters: 11797 * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes 11798 * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. 11799 * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your 11800 * label function returns null. empty strings _will_ be painted. 11801 * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. 11802 * id - optional id to use for later retrieval of this overlay. 11803 * 11804 * 11805 */ 11806 _jp.Overlays.Label = function (params) { 11807 this.labelStyle = params.labelStyle; 11808 11809 var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; 11810 this.cssClass = this.labelStyle != null ? this.labelStyle.cssClass : null; 11811 var p = _jp.extend({ 11812 create: function () { 11813 return _jp.createElement("div"); 11814 }}, params); 11815 _jp.Overlays.Custom.call(this, p); 11816 this.type = "Label"; 11817 this.label = params.label || ""; 11818 this.labelText = null; 11819 if (this.labelStyle) { 11820 var el = this.getElement(); 11821 this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; 11822 el.style.font = this.labelStyle.font; 11823 el.style.color = this.labelStyle.color || "black"; 11824 if (this.labelStyle.fill) { 11825 el.style.background = this.labelStyle.fill; 11826 } 11827 if (this.labelStyle.borderWidth > 0) { 11828 var dStyle = this.labelStyle.borderStyle ? this.labelStyle.borderStyle : "black"; 11829 el.style.border = this.labelStyle.borderWidth + "px solid " + dStyle; 11830 } 11831 if (this.labelStyle.padding) { 11832 el.style.padding = this.labelStyle.padding; 11833 } 11834 } 11835 11836 }; 11837 _ju.extend(_jp.Overlays.Label, _jp.Overlays.Custom, { 11838 cleanup: function (force) { 11839 if (force) { 11840 this.div = null; 11841 this.label = null; 11842 this.labelText = null; 11843 this.cssClass = null; 11844 this.labelStyle = null; 11845 } 11846 }, 11847 getLabel: function () { 11848 return this.label; 11849 }, 11850 /* 11851 * Function: setLabel 11852 * sets the label's, um, label. you would think i'd call this function 11853 * 'setText', but you can pass either a Function or a String to this, so 11854 * it makes more sense as 'setLabel'. This uses innerHTML on the label div, so keep 11855 * that in mind if you need escaped HTML. 11856 */ 11857 setLabel: function (l) { 11858 this.label = l; 11859 this.labelText = null; 11860 this.clearCachedDimensions(); 11861 this.update(); 11862 this.component.repaint(); 11863 }, 11864 getDimensions: function () { 11865 this.update(); 11866 return AbstractDOMOverlay.prototype.getDimensions.apply(this, arguments); 11867 }, 11868 update: function () { 11869 if (typeof this.label === "function") { 11870 var lt = this.label(this); 11871 this.getElement().innerHTML = lt.replace(/\r\n/g, "<br/>"); 11872 } 11873 else { 11874 if (this.labelText == null) { 11875 this.labelText = this.label; 11876 this.getElement().innerHTML = this.labelText.replace(/\r\n/g, "<br/>"); 11877 } 11878 } 11879 }, 11880 updateFrom:function(d) { 11881 if(d.label != null){ 11882 this.setLabel(d.label); 11883 } 11884 } 11885 }); 11886 11887 // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** 11888 11889 }).call(typeof window !== 'undefined' ? window : this); 11890 11891 /* 11892 * jsPlumb Community Edition 11893 * 11894 * Provides a way to visually connect elements on an HTML page, using SVG. 11895 * 11896 * This file contains the base class for library adapters. 11897 * 11898 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 11899 * 11900 * https://jsplumbtoolkit.com 11901 * https://github.com/jsplumb/jsplumb 11902 * 11903 * Dual licensed under the MIT and GPL2 licenses. 11904 */ 11905 ;(function() { 11906 "use strict"; 11907 var root = this, 11908 _jp = root.jsPlumb; 11909 11910 var _getEventManager = function(instance) { 11911 var e = instance._mottle; 11912 if (!e) { 11913 e = instance._mottle = new root.Mottle(); 11914 } 11915 return e; 11916 }; 11917 11918 _jp.extend(root.jsPlumbInstance.prototype, { 11919 getEventManager:function() { 11920 return _getEventManager(this); 11921 }, 11922 on : function(el, event, callback) { 11923 // TODO: here we would like to map the tap event if we know its 11924 // an internal bind to a click. we have to know its internal because only 11925 // then can we be sure that the UP event wont be consumed (tap is a synthesized 11926 // event from a mousedown followed by a mouseup). 11927 //event = { "click":"tap", "dblclick":"dbltap"}[event] || event; 11928 this.getEventManager().on.apply(this, arguments); 11929 return this; 11930 }, 11931 off : function(el, event, callback) { 11932 this.getEventManager().off.apply(this, arguments); 11933 return this; 11934 } 11935 }); 11936 11937 11938 }).call(typeof window !== 'undefined' ? window : this); 11939 /* 11940 * jsPlumb Community Edition 11941 * 11942 * Provides a way to visually connect elements on an HTML page, using SVG. 11943 * 11944 * This file contains the code for working with Groups. 11945 * 11946 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 11947 * 11948 * https://jsplumbtoolkit.com 11949 * https://github.com/jsplumb/jsplumb 11950 * 11951 * Dual licensed under the MIT and GPL2 licenses. 11952 */ 11953 ;(function() { 11954 "use strict"; 11955 11956 var root = this, 11957 _ju = root.jsPlumbUtil, 11958 _jpi = root.jsPlumbInstance; 11959 11960 var GROUP_COLLAPSED_CLASS = "jtk-group-collapsed"; 11961 var GROUP_EXPANDED_CLASS = "jtk-group-expanded"; 11962 var GROUP_CONTAINER_SELECTOR = "[jtk-group-content]"; 11963 var ELEMENT_DRAGGABLE_EVENT = "elementDraggable"; 11964 var STOP = "stop"; 11965 var REVERT = "revert"; 11966 var GROUP_MANAGER = "_groupManager"; 11967 var GROUP = "_jsPlumbGroup"; 11968 var GROUP_DRAG_SCOPE = "_jsPlumbGroupDrag"; 11969 var EVT_CHILD_ADDED = "group:addMember"; 11970 var EVT_CHILD_REMOVED = "group:removeMember"; 11971 var EVT_GROUP_ADDED = "group:add"; 11972 var EVT_GROUP_REMOVED = "group:remove"; 11973 var EVT_EXPAND = "group:expand"; 11974 var EVT_COLLAPSE = "group:collapse"; 11975 var EVT_GROUP_DRAG_STOP = "groupDragStop"; 11976 var EVT_CONNECTION_MOVED = "connectionMoved"; 11977 var EVT_INTERNAL_CONNECTION_DETACHED = "internal.connectionDetached"; 11978 11979 var CMD_REMOVE_ALL = "removeAll"; 11980 var CMD_ORPHAN_ALL = "orphanAll"; 11981 var CMD_SHOW = "show"; 11982 var CMD_HIDE = "hide"; 11983 11984 var GroupManager = function(_jsPlumb) { 11985 var _managedGroups = {}, _connectionSourceMap = {}, _connectionTargetMap = {}, self = this; 11986 11987 _jsPlumb.bind("connection", function(p) { 11988 if (p.source[GROUP] != null && p.target[GROUP] != null && p.source[GROUP] === p.target[GROUP]) { 11989 _connectionSourceMap[p.connection.id] = p.source[GROUP]; 11990 _connectionTargetMap[p.connection.id] = p.source[GROUP]; 11991 } 11992 else { 11993 if (p.source[GROUP] != null) { 11994 _ju.suggest(p.source[GROUP].connections.source, p.connection); 11995 _connectionSourceMap[p.connection.id] = p.source[GROUP]; 11996 } 11997 if (p.target[GROUP] != null) { 11998 _ju.suggest(p.target[GROUP].connections.target, p.connection); 11999 _connectionTargetMap[p.connection.id] = p.target[GROUP]; 12000 } 12001 } 12002 }); 12003 12004 function _cleanupDetachedConnection(conn) { 12005 delete conn.proxies; 12006 var group = _connectionSourceMap[conn.id], f; 12007 if (group != null) { 12008 f = function(c) { return c.id === conn.id; }; 12009 _ju.removeWithFunction(group.connections.source, f); 12010 _ju.removeWithFunction(group.connections.target, f); 12011 delete _connectionSourceMap[conn.id]; 12012 } 12013 12014 group = _connectionTargetMap[conn.id]; 12015 if (group != null) { 12016 f = function(c) { return c.id === conn.id; }; 12017 _ju.removeWithFunction(group.connections.source, f); 12018 _ju.removeWithFunction(group.connections.target, f); 12019 delete _connectionTargetMap[conn.id]; 12020 } 12021 } 12022 12023 _jsPlumb.bind(EVT_INTERNAL_CONNECTION_DETACHED, function(p) { 12024 _cleanupDetachedConnection(p.connection); 12025 }); 12026 12027 _jsPlumb.bind(EVT_CONNECTION_MOVED, function(p) { 12028 var connMap = p.index === 0 ? _connectionSourceMap : _connectionTargetMap; 12029 var group = connMap[p.connection.id]; 12030 if (group) { 12031 var list = group.connections[p.index === 0 ? "source" : "target"]; 12032 var idx = list.indexOf(p.connection); 12033 if (idx !== -1) { 12034 list.splice(idx, 1); 12035 } 12036 } 12037 }); 12038 12039 this.addGroup = function(group) { 12040 _jsPlumb.addClass(group.getEl(), GROUP_EXPANDED_CLASS); 12041 _managedGroups[group.id] = group; 12042 group.manager = this; 12043 _updateConnectionsForGroup(group); 12044 _jsPlumb.fire(EVT_GROUP_ADDED, { group:group }); 12045 }; 12046 12047 this.addToGroup = function(group, el, doNotFireEvent) { 12048 group = this.getGroup(group); 12049 if (group) { 12050 //group.add(el, doNotFireEvent); 12051 var groupEl = group.getEl(); 12052 12053 if (el._isJsPlumbGroup) { 12054 return; 12055 } 12056 var currentGroup = el._jsPlumbGroup; 12057 // if already a member of this group, do nothing 12058 if (currentGroup !== group) { 12059 var elpos = _jsPlumb.getOffset(el, true); 12060 var cpos = group.collapsed ? _jsPlumb.getOffset(groupEl, true) : _jsPlumb.getOffset(group.getDragArea(), true); 12061 12062 // otherwise, transfer to this group. 12063 if (currentGroup != null) { 12064 currentGroup.remove(el, doNotFireEvent); 12065 self.updateConnectionsForGroup(currentGroup); 12066 } 12067 group.add(el, doNotFireEvent); 12068 12069 var handleDroppedConnections = function (list, index) { 12070 var oidx = index === 0 ? 1 : 0; 12071 list.each(function (c) { 12072 c.setVisible(false); 12073 if (c.endpoints[oidx].element._jsPlumbGroup === group) { 12074 c.endpoints[oidx].setVisible(false); 12075 self.expandConnection(c, oidx, group); 12076 } 12077 else { 12078 c.endpoints[index].setVisible(false); 12079 self.collapseConnection(c, index, group); 12080 } 12081 }); 12082 }; 12083 12084 if (group.collapsed) { 12085 handleDroppedConnections(_jsPlumb.select({source: el}), 0); 12086 handleDroppedConnections(_jsPlumb.select({target: el}), 1); 12087 } 12088 12089 var elId = _jsPlumb.getId(el); 12090 _jsPlumb.dragManager.setParent(el, elId, groupEl, _jsPlumb.getId(groupEl), elpos); 12091 12092 var newPosition = { left: elpos.left - cpos.left, top: elpos.top - cpos.top }; 12093 12094 _jsPlumb.setPosition(el, newPosition); 12095 12096 _jsPlumb.dragManager.revalidateParent(el, elId, elpos); 12097 12098 self.updateConnectionsForGroup(group); 12099 12100 _jsPlumb.revalidate(elId); 12101 12102 setTimeout(function () { 12103 _jsPlumb.fire(EVT_CHILD_ADDED, {group: group, el: el}); 12104 }, 0); 12105 } 12106 } 12107 }; 12108 12109 this.removeFromGroup = function(group, el, doNotFireEvent) { 12110 group = this.getGroup(group); 12111 if (group) { 12112 group.remove(el, null, doNotFireEvent); 12113 } 12114 }; 12115 12116 this.getGroup = function(groupId) { 12117 var group = groupId; 12118 if (_ju.isString(groupId)) { 12119 group = _managedGroups[groupId]; 12120 if (group == null) { 12121 throw new TypeError("No such group [" + groupId + "]"); 12122 } 12123 } 12124 return group; 12125 }; 12126 12127 this.getGroups = function() { 12128 var o = []; 12129 for (var g in _managedGroups) { 12130 o.push(_managedGroups[g]); 12131 } 12132 return o; 12133 }; 12134 12135 this.removeGroup = function(group, deleteMembers, manipulateDOM, doNotFireEvent) { 12136 group = this.getGroup(group); 12137 this.expandGroup(group, true); // this reinstates any original connections and removes all proxies, but does not fire an event. 12138 group[deleteMembers ? CMD_REMOVE_ALL : CMD_ORPHAN_ALL](manipulateDOM, doNotFireEvent); 12139 _jsPlumb.remove(group.getEl()); 12140 delete _managedGroups[group.id]; 12141 delete _jsPlumb._groups[group.id]; 12142 _jsPlumb.fire(EVT_GROUP_REMOVED, { group:group }); 12143 }; 12144 12145 this.removeAllGroups = function(deleteMembers, manipulateDOM, doNotFireEvent) { 12146 for (var g in _managedGroups) { 12147 this.removeGroup(_managedGroups[g], deleteMembers, manipulateDOM, doNotFireEvent); 12148 } 12149 }; 12150 12151 function _setVisible(group, state) { 12152 var m = group.getMembers(); 12153 for (var i = 0; i < m.length; i++) { 12154 _jsPlumb[state ? CMD_SHOW : CMD_HIDE](m[i], true); 12155 } 12156 } 12157 12158 var _collapseConnection = this.collapseConnection = function(c, index, group) { 12159 12160 var proxyEp, groupEl = group.getEl(), groupElId = _jsPlumb.getId(groupEl), 12161 originalElementId = c.endpoints[index].elementId; 12162 12163 var otherEl = c.endpoints[index === 0 ? 1 : 0].element; 12164 if (otherEl[GROUP] && (!otherEl[GROUP].shouldProxy() && otherEl[GROUP].collapsed)) { 12165 return; 12166 } 12167 12168 c.proxies = c.proxies || []; 12169 if(c.proxies[index]) { 12170 proxyEp = c.proxies[index].ep; 12171 }else { 12172 proxyEp = _jsPlumb.addEndpoint(groupEl, { 12173 endpoint:group.getEndpoint(c, index), 12174 anchor:group.getAnchor(c, index), 12175 parameters:{ 12176 isProxyEndpoint:true 12177 } 12178 }); 12179 } 12180 proxyEp.setDeleteOnEmpty(true); 12181 12182 // for this index, stash proxy info: the new EP, the original EP. 12183 c.proxies[index] = { ep:proxyEp, originalEp: c.endpoints[index] }; 12184 12185 // and advise the anchor manager 12186 if (index === 0) { 12187 // TODO why are there two differently named methods? Why is there not one method that says "some end of this 12188 // connection changed (you give the index), and here's the new element and element id." 12189 _jsPlumb.anchorManager.sourceChanged(originalElementId, groupElId, c, groupEl); 12190 } 12191 else { 12192 _jsPlumb.anchorManager.updateOtherEndpoint(c.endpoints[0].elementId, originalElementId, groupElId, c); 12193 c.target = groupEl; 12194 c.targetId = groupElId; 12195 } 12196 12197 12198 // detach the original EP from the connection. 12199 c.proxies[index].originalEp.detachFromConnection(c, null, true); 12200 12201 // set the proxy as the new ep 12202 proxyEp.connections = [ c ]; 12203 c.endpoints[index] = proxyEp; 12204 12205 c.setVisible(true); 12206 }; 12207 12208 this.collapseGroup = function(group) { 12209 group = this.getGroup(group); 12210 if (group == null || group.collapsed) { 12211 return; 12212 } 12213 var groupEl = group.getEl(); 12214 12215 // todo remove old proxy endpoints first, just in case? 12216 //group.proxies.length = 0; 12217 12218 // hide all connections 12219 _setVisible(group, false); 12220 12221 if (group.shouldProxy()) { 12222 // collapses all connections in a group. 12223 var _collapseSet = function (conns, index) { 12224 for (var i = 0; i < conns.length; i++) { 12225 var c = conns[i]; 12226 _collapseConnection(c, index, group); 12227 } 12228 }; 12229 12230 // setup proxies for sources and targets 12231 _collapseSet(group.connections.source, 0); 12232 _collapseSet(group.connections.target, 1); 12233 } 12234 12235 group.collapsed = true; 12236 _jsPlumb.removeClass(groupEl, GROUP_EXPANDED_CLASS); 12237 _jsPlumb.addClass(groupEl, GROUP_COLLAPSED_CLASS); 12238 _jsPlumb.revalidate(groupEl); 12239 _jsPlumb.fire(EVT_COLLAPSE, { group:group }); 12240 }; 12241 12242 var _expandConnection = this.expandConnection = function(c, index, group) { 12243 12244 // if no proxies or none for this end of the connection, abort. 12245 if (c.proxies == null || c.proxies[index] == null) { 12246 return; 12247 } 12248 12249 var groupElId = _jsPlumb.getId(group.getEl()), 12250 originalElement = c.proxies[index].originalEp.element, 12251 originalElementId = c.proxies[index].originalEp.elementId; 12252 12253 c.endpoints[index] = c.proxies[index].originalEp; 12254 // and advise the anchor manager 12255 if (index === 0) { 12256 // TODO why are there two differently named methods? Why is there not one method that says "some end of this 12257 // connection changed (you give the index), and here's the new element and element id." 12258 _jsPlumb.anchorManager.sourceChanged(groupElId, originalElementId, c, originalElement); 12259 } 12260 else { 12261 _jsPlumb.anchorManager.updateOtherEndpoint(c.endpoints[0].elementId, groupElId, originalElementId, c); 12262 c.target = originalElement; 12263 c.targetId = originalElementId; 12264 } 12265 12266 // detach the proxy EP from the connection (which will cause it to be removed as we no longer need it) 12267 c.proxies[index].ep.detachFromConnection(c, null); 12268 12269 c.proxies[index].originalEp.addConnection(c); 12270 12271 // cleanup 12272 delete c.proxies[index]; 12273 }; 12274 12275 this.expandGroup = function(group, doNotFireEvent) { 12276 12277 group = this.getGroup(group); 12278 12279 if (group == null || !group.collapsed) { 12280 return; 12281 } 12282 var groupEl = group.getEl(); 12283 12284 _setVisible(group, true); 12285 12286 if (group.shouldProxy()) { 12287 // collapses all connections in a group. 12288 var _expandSet = function (conns, index) { 12289 for (var i = 0; i < conns.length; i++) { 12290 var c = conns[i]; 12291 _expandConnection(c, index, group); 12292 } 12293 }; 12294 12295 // setup proxies for sources and targets 12296 _expandSet(group.connections.source, 0); 12297 _expandSet(group.connections.target, 1); 12298 } 12299 12300 group.collapsed = false; 12301 _jsPlumb.addClass(groupEl, GROUP_EXPANDED_CLASS); 12302 _jsPlumb.removeClass(groupEl, GROUP_COLLAPSED_CLASS); 12303 _jsPlumb.revalidate(groupEl); 12304 this.repaintGroup(group); 12305 if (!doNotFireEvent) { 12306 _jsPlumb.fire(EVT_EXPAND, { group: group}); 12307 } 12308 }; 12309 12310 this.repaintGroup = function(group) { 12311 group = this.getGroup(group); 12312 var m = group.getMembers(); 12313 for (var i = 0; i < m.length; i++) { 12314 _jsPlumb.revalidate(m[i]); 12315 } 12316 }; 12317 12318 // TODO refactor this with the code that responds to `connection` events. 12319 function _updateConnectionsForGroup(group) { 12320 var members = group.getMembers(); 12321 var c1 = _jsPlumb.getConnections({source:members}, true); 12322 var c2 = _jsPlumb.getConnections({target:members}, true); 12323 var processed = {}; 12324 group.connections.source.length = 0; 12325 group.connections.target.length = 0; 12326 var oneSet = function(c) { 12327 for (var i = 0; i < c.length; i++) { 12328 if (processed[c[i].id]) { 12329 continue; 12330 } 12331 processed[c[i].id] = true; 12332 if (c[i].source._jsPlumbGroup === group) { 12333 if (c[i].target._jsPlumbGroup !== group) { 12334 group.connections.source.push(c[i]); 12335 } 12336 _connectionSourceMap[c[i].id] = group; 12337 } 12338 else if (c[i].target._jsPlumbGroup === group) { 12339 group.connections.target.push(c[i]); 12340 _connectionTargetMap[c[i].id] = group; 12341 } 12342 } 12343 }; 12344 oneSet(c1); oneSet(c2); 12345 } 12346 12347 this.updateConnectionsForGroup = _updateConnectionsForGroup; 12348 this.refreshAllGroups = function() { 12349 for (var g in _managedGroups) { 12350 _updateConnectionsForGroup(_managedGroups[g]); 12351 _jsPlumb.dragManager.updateOffsets(_jsPlumb.getId(_managedGroups[g].getEl())); 12352 } 12353 }; 12354 }; 12355 12356 /** 12357 * 12358 * @param {jsPlumbInstance} _jsPlumb Associated jsPlumb instance. 12359 * @param {Object} params 12360 * @param {Element} params.el The DOM element representing the Group. 12361 * @param {String} [params.id] Optional ID for the Group. A UUID will be assigned as the Group's ID if you do not provide one. 12362 * @param {Boolean} [params.constrain=false] If true, child elements will not be able to be dragged outside of the Group container. 12363 * @param {Boolean} [params.revert=true] By default, child elements revert to the container if dragged outside. You can change this by setting `revert:false`. This behaviour is also overridden if you set `orphan` or `prune`. 12364 * @param {Boolean} [params.orphan=false] If true, child elements dropped outside of the Group container will be removed from the Group (but not from the DOM). 12365 * @param {Boolean} [params.prune=false] If true, child elements dropped outside of the Group container will be removed from the Group and also from the DOM. 12366 * @param {Boolean} [params.dropOverride=false] If true, a child element that has been dropped onto some other Group will not be subject to the controls imposed by `prune`, `revert` or `orphan`. 12367 * @constructor 12368 */ 12369 var Group = function(_jsPlumb, params) { 12370 var self = this; 12371 var el = params.el; 12372 this.getEl = function() { return el; }; 12373 this.id = params.id || _ju.uuid(); 12374 el._isJsPlumbGroup = true; 12375 12376 var getDragArea = this.getDragArea = function() { 12377 var da = _jsPlumb.getSelector(el, GROUP_CONTAINER_SELECTOR); 12378 return da && da.length > 0 ? da[0] : el; 12379 }; 12380 12381 var ghost = params.ghost === true; 12382 var constrain = ghost || (params.constrain === true); 12383 var revert = params.revert !== false; 12384 var orphan = params.orphan === true; 12385 var prune = params.prune === true; 12386 var dropOverride = params.dropOverride === true; 12387 var proxied = params.proxied !== false; 12388 var elements = []; 12389 this.connections = { source:[], target:[], internal:[] }; 12390 12391 // this function, and getEndpoint below, are stubs for a future setup in which we can choose endpoint 12392 // and anchor based upon the connection and the index (source/target) of the endpoint to be proxied. 12393 this.getAnchor = function(conn, endpointIndex) { 12394 return params.anchor || "Continuous"; 12395 }; 12396 12397 this.getEndpoint = function(conn, endpointIndex) { 12398 return params.endpoint || [ "Dot", { radius:10 }]; 12399 }; 12400 12401 this.collapsed = false; 12402 if (params.draggable !== false) { 12403 var opts = { 12404 stop:function(params) { 12405 _jsPlumb.fire(EVT_GROUP_DRAG_STOP, jsPlumb.extend(params, {group:self})); 12406 }, 12407 scope:GROUP_DRAG_SCOPE 12408 }; 12409 if (params.dragOptions) { 12410 root.jsPlumb.extend(opts, params.dragOptions); 12411 } 12412 _jsPlumb.draggable(params.el, opts); 12413 } 12414 if (params.droppable !== false) { 12415 _jsPlumb.droppable(params.el, { 12416 drop:function(p) { 12417 var el = p.drag.el; 12418 if (el._isJsPlumbGroup) { 12419 return; 12420 } 12421 var currentGroup = el._jsPlumbGroup; 12422 if (currentGroup !== self) { 12423 if (currentGroup != null) { 12424 if (currentGroup.overrideDrop(el, self)) { 12425 return; 12426 } 12427 } 12428 _jsPlumb.getGroupManager().addToGroup(self, el, false); 12429 } 12430 12431 } 12432 }); 12433 } 12434 var _each = function(_el, fn) { 12435 var els = _el.nodeType == null ? _el : [ _el ]; 12436 for (var i = 0; i < els.length; i++) { 12437 fn(els[i]); 12438 } 12439 }; 12440 12441 this.overrideDrop = function(_el, targetGroup) { 12442 return dropOverride && (revert || prune || orphan); 12443 }; 12444 12445 this.add = function(_el, doNotFireEvent) { 12446 var dragArea = getDragArea(); 12447 _each(_el, function(__el) { 12448 12449 if (__el._jsPlumbGroup != null) { 12450 if (__el._jsPlumbGroup === self) { 12451 return; 12452 } else { 12453 __el._jsPlumbGroup.remove(__el, true, doNotFireEvent, false); 12454 } 12455 } 12456 12457 __el._jsPlumbGroup = self; 12458 elements.push(__el); 12459 // test if draggable and add handlers if so. 12460 if (_jsPlumb.isAlreadyDraggable(__el)) { 12461 _bindDragHandlers(__el); 12462 } 12463 12464 if (__el.parentNode !== dragArea) { 12465 dragArea.appendChild(__el); 12466 } 12467 12468 if (!doNotFireEvent) { 12469 _jsPlumb.fire(EVT_CHILD_ADDED, {group: self, el: __el}); 12470 } 12471 }); 12472 12473 _jsPlumb.getGroupManager().updateConnectionsForGroup(self); 12474 }; 12475 12476 this.remove = function(el, manipulateDOM, doNotFireEvent, doNotUpdateConnections) { 12477 12478 _each(el, function(__el) { 12479 delete __el._jsPlumbGroup; 12480 _ju.removeWithFunction(elements, function(e) { 12481 return e === __el; 12482 }); 12483 12484 if (manipulateDOM) { 12485 try { self.getDragArea().removeChild(__el); } 12486 catch (e) { 12487 jsPlumbUtil.log("Could not remove element from Group " + e); 12488 } 12489 } 12490 _unbindDragHandlers(__el); 12491 if (!doNotFireEvent) { 12492 _jsPlumb.fire(EVT_CHILD_REMOVED, {group: self, el: __el}); 12493 } 12494 }); 12495 if (!doNotUpdateConnections) { 12496 _jsPlumb.getGroupManager().updateConnectionsForGroup(self); 12497 } 12498 }; 12499 this.removeAll = function(manipulateDOM, doNotFireEvent) { 12500 for (var i = 0, l = elements.length; i < l; i++) { 12501 self.remove(elements[0], manipulateDOM, doNotFireEvent, true); 12502 } 12503 elements.length = 0; 12504 _jsPlumb.getGroupManager().updateConnectionsForGroup(self); 12505 }; 12506 this.orphanAll = function() { 12507 for (var i = 0; i < elements.length; i++) { 12508 _orphan(elements[i]); 12509 } 12510 elements.length = 0; 12511 }; 12512 this.getMembers = function() { return elements; }; 12513 12514 el[GROUP] = this; 12515 12516 _jsPlumb.bind(ELEMENT_DRAGGABLE_EVENT, function(dragParams) { 12517 // if its for the current group, 12518 if (dragParams.el._jsPlumbGroup === this) { 12519 _bindDragHandlers(dragParams.el); 12520 } 12521 }.bind(this)); 12522 12523 function _findParent(_el) { 12524 return _el.offsetParent; 12525 } 12526 12527 function _isInsideParent(_el, pos) { 12528 var p = _findParent(_el), 12529 s = _jsPlumb.getSize(p), 12530 ss = _jsPlumb.getSize(_el), 12531 leftEdge = pos[0], 12532 rightEdge = leftEdge + ss[0], 12533 topEdge = pos[1], 12534 bottomEdge = topEdge + ss[1]; 12535 12536 return rightEdge > 0 && leftEdge < s[0] && bottomEdge > 0 && topEdge < s[1]; 12537 } 12538 12539 // 12540 // orphaning an element means taking it out of the group and adding it to the main jsplumb container. 12541 // 12542 function _orphan(_el) { 12543 var id = _jsPlumb.getId(_el); 12544 var pos = _jsPlumb.getOffset(_el); 12545 _el.parentNode.removeChild(_el); 12546 _jsPlumb.getContainer().appendChild(_el); 12547 _jsPlumb.setPosition(_el, pos); 12548 delete _el._jsPlumbGroup; 12549 _unbindDragHandlers(_el); 12550 _jsPlumb.dragManager.clearParent(_el, id); 12551 } 12552 12553 // 12554 // remove an element from the group, then either prune it from the jsplumb instance, or just orphan it. 12555 // 12556 function _pruneOrOrphan(p) { 12557 if (!_isInsideParent(p.el, p.pos)) { 12558 var group = p.el._jsPlumbGroup; 12559 if (prune) { 12560 _jsPlumb.remove(p.el); 12561 } else { 12562 _orphan(p.el); 12563 } 12564 12565 group.remove(p.el); 12566 } 12567 } 12568 12569 // 12570 // redraws the element 12571 // 12572 function _revalidate(_el) { 12573 var id = _jsPlumb.getId(_el); 12574 _jsPlumb.revalidate(_el); 12575 _jsPlumb.dragManager.revalidateParent(_el, id); 12576 } 12577 12578 // 12579 // unbind the group specific drag/revert handlers. 12580 // 12581 function _unbindDragHandlers(_el) { 12582 if (!_el._katavorioDrag) { 12583 return; 12584 } 12585 if (prune || orphan) { 12586 _el._katavorioDrag.off(STOP, _pruneOrOrphan); 12587 } 12588 if (!prune && !orphan && revert) { 12589 _el._katavorioDrag.off(REVERT, _revalidate); 12590 _el._katavorioDrag.setRevert(null); 12591 } 12592 } 12593 12594 function _bindDragHandlers(_el) { 12595 if (!_el._katavorioDrag) { 12596 return; 12597 } 12598 if (prune || orphan) { 12599 _el._katavorioDrag.on(STOP, _pruneOrOrphan); 12600 } 12601 12602 if (constrain) { 12603 _el._katavorioDrag.setConstrain(true); 12604 } 12605 12606 if (ghost) { 12607 _el._katavorioDrag.setUseGhostProxy(true); 12608 } 12609 12610 if (!prune && !orphan && revert) { 12611 _el._katavorioDrag.on(REVERT, _revalidate); 12612 _el._katavorioDrag.setRevert(function(__el, pos) { 12613 return !_isInsideParent(__el, pos); 12614 }); 12615 } 12616 } 12617 12618 this.shouldProxy = function() { 12619 return proxied; 12620 }; 12621 12622 _jsPlumb.getGroupManager().addGroup(this); 12623 }; 12624 12625 /** 12626 * Adds a group to the jsPlumb instance. 12627 * @method addGroup 12628 * @param {Object} params 12629 * @return {Group} The newly created Group. 12630 */ 12631 _jpi.prototype.addGroup = function(params) { 12632 var j = this; 12633 j._groups = j._groups || {}; 12634 if (j._groups[params.id] != null) { 12635 throw new TypeError("cannot create Group [" + params.id + "]; a Group with that ID exists"); 12636 } 12637 if (params.el[GROUP] != null) { 12638 throw new TypeError("cannot create Group [" + params.id + "]; the given element is already a Group"); 12639 } 12640 var group = new Group(j, params); 12641 j._groups[group.id] = group; 12642 if (params.collapsed) { 12643 this.collapseGroup(group); 12644 } 12645 return group; 12646 }; 12647 12648 /** 12649 * Add an element to a group. 12650 * @method addToGroup 12651 * @param {String} group Group, or ID of the group, to add the element to. 12652 * @param {Element} el Element to add to the group. 12653 */ 12654 _jpi.prototype.addToGroup = function(group, el, doNotFireEvent) { 12655 12656 var _one = function(_el) { 12657 var id = this.getId(_el); 12658 this.manage(id, _el); 12659 this.getGroupManager().addToGroup(group, _el, doNotFireEvent); 12660 }.bind(this); 12661 12662 if (Array.isArray(el)) { 12663 for (var i = 0; i < el.length; i++) { 12664 _one(el[i]); 12665 } 12666 } else { 12667 _one(el); 12668 } 12669 }; 12670 12671 /** 12672 * Remove an element from a group. 12673 * @method removeFromGroup 12674 * @param {String} group Group, or ID of the group, to remove the element from. 12675 * @param {Element} el Element to add to the group. 12676 */ 12677 _jpi.prototype.removeFromGroup = function(group, el, doNotFireEvent) { 12678 this.getGroupManager().removeFromGroup(group, el, doNotFireEvent); 12679 }; 12680 12681 /** 12682 * Remove a group, and optionally remove its members from the jsPlumb instance. 12683 * @method removeGroup 12684 * @param {String|Group} group Group to delete, or ID of Group to delete. 12685 * @param {Boolean} [deleteMembers=false] If true, group members will be removed along with the group. Otherwise they will 12686 * just be 'orphaned' (returned to the main container). 12687 */ 12688 _jpi.prototype.removeGroup = function(group, deleteMembers, manipulateDOM, doNotFireEvent) { 12689 this.getGroupManager().removeGroup(group, deleteMembers, manipulateDOM, doNotFireEvent); 12690 }; 12691 12692 /** 12693 * Remove all groups, and optionally remove their members from the jsPlumb instance. 12694 * @method removeAllGroup 12695 * @param {Boolean} [deleteMembers=false] If true, group members will be removed along with the groups. Otherwise they will 12696 * just be 'orphaned' (returned to the main container). 12697 */ 12698 _jpi.prototype.removeAllGroups = function(deleteMembers, manipulateDOM, doNotFireEvent) { 12699 this.getGroupManager().removeAllGroups(deleteMembers, manipulateDOM, doNotFireEvent); 12700 }; 12701 12702 /** 12703 * Get a Group 12704 * @method getGroup 12705 * @param {String} groupId ID of the group to get 12706 * @return {Group} Group with the given ID, null if not found. 12707 */ 12708 _jpi.prototype.getGroup = function(groupId) { 12709 return this.getGroupManager().getGroup(groupId); 12710 }; 12711 12712 /** 12713 * Gets all the Groups managed by the jsPlumb instance. 12714 * @returns {Group[]} List of Groups. Empty if none. 12715 */ 12716 _jpi.prototype.getGroups = function() { 12717 return this.getGroupManager().getGroups(); 12718 }; 12719 12720 /** 12721 * Expands a group element. jsPlumb doesn't do "everything" for you here, because what it means to expand a Group 12722 * will vary from application to application. jsPlumb does these things: 12723 * 12724 * - Hides any connections that are internal to the group (connections between members, and connections from member of 12725 * the group to the group itself) 12726 * - Proxies all connections for which the source or target is a member of the group. 12727 * - Hides the proxied connections. 12728 * - Adds the jtk-group-expanded class to the group's element 12729 * - Removes the jtk-group-collapsed class from the group's element. 12730 * 12731 * @method expandGroup 12732 * @param {String|Group} group Group to expand, or ID of Group to expand. 12733 */ 12734 _jpi.prototype.expandGroup = function(group) { 12735 this.getGroupManager().expandGroup(group); 12736 }; 12737 12738 /** 12739 * Collapses a group element. jsPlumb doesn't do "everything" for you here, because what it means to collapse a Group 12740 * will vary from application to application. jsPlumb does these things: 12741 * 12742 * - Shows any connections that are internal to the group (connections between members, and connections from member of 12743 * the group to the group itself) 12744 * - Removes proxies for all connections for which the source or target is a member of the group. 12745 * - Shows the previously proxied connections. 12746 * - Adds the jtk-group-collapsed class to the group's element 12747 * - Removes the jtk-group-expanded class from the group's element. 12748 * 12749 * @method expandGroup 12750 * @param {String|Group} group Group to expand, or ID of Group to expand. 12751 */ 12752 _jpi.prototype.collapseGroup = function(groupId) { 12753 this.getGroupManager().collapseGroup(groupId); 12754 }; 12755 12756 12757 _jpi.prototype.repaintGroup = function(group) { 12758 this.getGroupManager().repaintGroup(group); 12759 }; 12760 12761 /** 12762 * Collapses or expands a group element depending on its current state. See notes in the collapseGroup and expandGroup method. 12763 * 12764 * @method toggleGroup 12765 * @param {String|Group} group Group to expand/collapse, or ID of Group to expand/collapse. 12766 */ 12767 _jpi.prototype.toggleGroup = function(group) { 12768 group = this.getGroupManager().getGroup(group); 12769 if (group != null) { 12770 this.getGroupManager()[group.collapsed ? "expandGroup" : "collapseGroup"](group); 12771 } 12772 }; 12773 12774 // 12775 // lazy init a group manager for the given jsplumb instance. 12776 // 12777 _jpi.prototype.getGroupManager = function() { 12778 var mgr = this[GROUP_MANAGER]; 12779 if (mgr == null) { 12780 mgr = this[GROUP_MANAGER] = new GroupManager(this); 12781 } 12782 return mgr; 12783 }; 12784 12785 _jpi.prototype.removeGroupManager = function() { 12786 delete this[GROUP_MANAGER]; 12787 }; 12788 12789 /** 12790 * Gets the Group that the given element belongs to, null if none. 12791 * @method getGroupFor 12792 * @param {String|Element} el Element, or element ID. 12793 * @returns {Group} A Group, if found, or null. 12794 */ 12795 _jpi.prototype.getGroupFor = function(el) { 12796 el = this.getElement(el); 12797 if (el) { 12798 return el[GROUP]; 12799 } 12800 }; 12801 12802 }).call(typeof window !== 'undefined' ? window : this); 12803 12804 12805 /* 12806 * jsPlumb Community Edition 12807 * 12808 * Provides a way to visually connect elements on an HTML page, using SVG. 12809 * 12810 * This file contains the 'flowchart' connectors, consisting of vertical and horizontal line segments. 12811 * 12812 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 12813 * 12814 * https://jsplumbtoolkit.com 12815 * https://github.com/jsplumb/jsplumb 12816 * 12817 * Dual licensed under the MIT and GPL2 licenses. 12818 */ 12819 ; 12820 (function () { 12821 12822 "use strict"; 12823 var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil; 12824 12825 var Flowchart = function (params) { 12826 this.type = "Flowchart"; 12827 params = params || {}; 12828 params.stub = params.stub == null ? 30 : params.stub; 12829 var segments, 12830 _super = _jp.Connectors.AbstractConnector.apply(this, arguments), 12831 midpoint = params.midpoint == null ? 0.5 : params.midpoint, 12832 alwaysRespectStubs = params.alwaysRespectStubs === true, 12833 lastx = null, lasty = null, lastOrientation, 12834 cornerRadius = params.cornerRadius != null ? params.cornerRadius : 0, 12835 12836 // TODO now common between this and AbstractBezierEditor; refactor into superclass? 12837 loopbackRadius = params.loopbackRadius || 25, 12838 isLoopbackCurrently = false, 12839 12840 sgn = function (n) { 12841 return n < 0 ? -1 : n === 0 ? 0 : 1; 12842 }, 12843 /** 12844 * helper method to add a segment. 12845 */ 12846 addSegment = function (segments, x, y, paintInfo) { 12847 if (lastx === x && lasty === y) { 12848 return; 12849 } 12850 var lx = lastx == null ? paintInfo.sx : lastx, 12851 ly = lasty == null ? paintInfo.sy : lasty, 12852 o = lx === x ? "v" : "h", 12853 sgnx = sgn(x - lx), 12854 sgny = sgn(y - ly); 12855 12856 lastx = x; 12857 lasty = y; 12858 segments.push([lx, ly, x, y, o, sgnx, sgny]); 12859 }, 12860 segLength = function (s) { 12861 return Math.sqrt(Math.pow(s[0] - s[2], 2) + Math.pow(s[1] - s[3], 2)); 12862 }, 12863 _cloneArray = function (a) { 12864 var _a = []; 12865 _a.push.apply(_a, a); 12866 return _a; 12867 }, 12868 writeSegments = function (conn, segments, paintInfo) { 12869 var current = null, next; 12870 for (var i = 0; i < segments.length - 1; i++) { 12871 12872 current = current || _cloneArray(segments[i]); 12873 next = _cloneArray(segments[i + 1]); 12874 if (cornerRadius > 0 && current[4] !== next[4]) { 12875 var radiusToUse = Math.min(cornerRadius, segLength(current), segLength(next)); 12876 // right angle. adjust current segment's end point, and next segment's start point. 12877 current[2] -= current[5] * radiusToUse; 12878 current[3] -= current[6] * radiusToUse; 12879 next[0] += next[5] * radiusToUse; 12880 next[1] += next[6] * radiusToUse; 12881 var ac = (current[6] === next[5] && next[5] === 1) || 12882 ((current[6] === next[5] && next[5] === 0) && current[5] !== next[6]) || 12883 (current[6] === next[5] && next[5] === -1), 12884 sgny = next[1] > current[3] ? 1 : -1, 12885 sgnx = next[0] > current[2] ? 1 : -1, 12886 sgnEqual = sgny === sgnx, 12887 cx = (sgnEqual && ac || (!sgnEqual && !ac)) ? next[0] : current[2], 12888 cy = (sgnEqual && ac || (!sgnEqual && !ac)) ? current[3] : next[1]; 12889 12890 _super.addSegment(conn, "Straight", { 12891 x1: current[0], y1: current[1], x2: current[2], y2: current[3] 12892 }); 12893 12894 _super.addSegment(conn, "Arc", { 12895 r: radiusToUse, 12896 x1: current[2], 12897 y1: current[3], 12898 x2: next[0], 12899 y2: next[1], 12900 cx: cx, 12901 cy: cy, 12902 ac: ac 12903 }); 12904 } 12905 else { 12906 // dx + dy are used to adjust for line width. 12907 var dx = (current[2] === current[0]) ? 0 : (current[2] > current[0]) ? (paintInfo.lw / 2) : -(paintInfo.lw / 2), 12908 dy = (current[3] === current[1]) ? 0 : (current[3] > current[1]) ? (paintInfo.lw / 2) : -(paintInfo.lw / 2); 12909 _super.addSegment(conn, "Straight", { 12910 x1: current[0] - dx, y1: current[1] - dy, x2: current[2] + dx, y2: current[3] + dy 12911 }); 12912 } 12913 current = next; 12914 } 12915 if (next != null) { 12916 // last segment 12917 _super.addSegment(conn, "Straight", { 12918 x1: next[0], y1: next[1], x2: next[2], y2: next[3] 12919 }); 12920 } 12921 }; 12922 12923 this._compute = function (paintInfo, params) { 12924 12925 segments = []; 12926 lastx = null; 12927 lasty = null; 12928 lastOrientation = null; 12929 12930 var commonStubCalculator = function () { 12931 return [ paintInfo.startStubX, paintInfo.startStubY, paintInfo.endStubX, paintInfo.endStubY ]; 12932 }, 12933 stubCalculators = { 12934 perpendicular: commonStubCalculator, 12935 orthogonal: commonStubCalculator, 12936 opposite: function (axis) { 12937 var pi = paintInfo, 12938 idx = axis === "x" ? 0 : 1, 12939 areInProximity = { 12940 "x": function () { 12941 return ( (pi.so[idx] === 1 && ( 12942 ( (pi.startStubX > pi.endStubX) && (pi.tx > pi.startStubX) ) || 12943 ( (pi.sx > pi.endStubX) && (pi.tx > pi.sx))))) || 12944 12945 ( (pi.so[idx] === -1 && ( 12946 ( (pi.startStubX < pi.endStubX) && (pi.tx < pi.startStubX) ) || 12947 ( (pi.sx < pi.endStubX) && (pi.tx < pi.sx))))); 12948 }, 12949 "y": function () { 12950 return ( (pi.so[idx] === 1 && ( 12951 ( (pi.startStubY > pi.endStubY) && (pi.ty > pi.startStubY) ) || 12952 ( (pi.sy > pi.endStubY) && (pi.ty > pi.sy))))) || 12953 12954 ( (pi.so[idx] === -1 && ( 12955 ( (pi.startStubY < pi.endStubY) && (pi.ty < pi.startStubY) ) || 12956 ( (pi.sy < pi.endStubY) && (pi.ty < pi.sy))))); 12957 } 12958 }; 12959 12960 if (!alwaysRespectStubs && areInProximity[axis]()) { 12961 return { 12962 "x": [(paintInfo.sx + paintInfo.tx) / 2, paintInfo.startStubY, (paintInfo.sx + paintInfo.tx) / 2, paintInfo.endStubY], 12963 "y": [paintInfo.startStubX, (paintInfo.sy + paintInfo.ty) / 2, paintInfo.endStubX, (paintInfo.sy + paintInfo.ty) / 2] 12964 }[axis]; 12965 } 12966 else { 12967 return [ paintInfo.startStubX, paintInfo.startStubY, paintInfo.endStubX, paintInfo.endStubY ]; 12968 } 12969 } 12970 }; 12971 12972 // calculate Stubs. 12973 var stubs = stubCalculators[paintInfo.anchorOrientation](paintInfo.sourceAxis), 12974 idx = paintInfo.sourceAxis === "x" ? 0 : 1, 12975 oidx = paintInfo.sourceAxis === "x" ? 1 : 0, 12976 ss = stubs[idx], 12977 oss = stubs[oidx], 12978 es = stubs[idx + 2], 12979 oes = stubs[oidx + 2]; 12980 12981 // add the start stub segment. use stubs for loopback as it will look better, with the loop spaced 12982 // away from the element. 12983 addSegment(segments, stubs[0], stubs[1], paintInfo); 12984 12985 // if its a loopback and we should treat it differently. 12986 if (false &¶ms.sourcePos[0] === params.targetPos[0] && params.sourcePos[1] === params.targetPos[1]) { 12987 12988 // we use loopbackRadius here, as statemachine connectors do. 12989 // so we go radius to the left from stubs[0], then upwards by 2*radius, to the right by 2*radius, 12990 // down by 2*radius, left by radius. 12991 addSegment(segments, stubs[0] - loopbackRadius, stubs[1], paintInfo); 12992 addSegment(segments, stubs[0] - loopbackRadius, stubs[1] - (2 * loopbackRadius), paintInfo); 12993 addSegment(segments, stubs[0] + loopbackRadius, stubs[1] - (2 * loopbackRadius), paintInfo); 12994 addSegment(segments, stubs[0] + loopbackRadius, stubs[1], paintInfo); 12995 addSegment(segments, stubs[0], stubs[1], paintInfo); 12996 12997 } 12998 else { 12999 13000 13001 var midx = paintInfo.startStubX + ((paintInfo.endStubX - paintInfo.startStubX) * midpoint), 13002 midy = paintInfo.startStubY + ((paintInfo.endStubY - paintInfo.startStubY) * midpoint); 13003 13004 var orientations = { x: [ 0, 1 ], y: [ 1, 0 ] }, 13005 lineCalculators = { 13006 perpendicular: function (axis) { 13007 var pi = paintInfo, 13008 sis = { 13009 x: [ 13010 [ [ 1, 2, 3, 4 ], null, [ 2, 1, 4, 3 ] ], 13011 null, 13012 [ [ 4, 3, 2, 1 ], null, [ 3, 4, 1, 2 ] ] 13013 ], 13014 y: [ 13015 [ [ 3, 2, 1, 4 ], null, [ 2, 3, 4, 1 ] ], 13016 null, 13017 [ [ 4, 1, 2, 3 ], null, [ 1, 4, 3, 2 ] ] 13018 ] 13019 }, 13020 stubs = { 13021 x: [ [ pi.startStubX, pi.endStubX ], null, [ pi.endStubX, pi.startStubX ] ], 13022 y: [ [ pi.startStubY, pi.endStubY ], null, [ pi.endStubY, pi.startStubY ] ] 13023 }, 13024 midLines = { 13025 x: [ [ midx, pi.startStubY ], [ midx, pi.endStubY ] ], 13026 y: [ [ pi.startStubX, midy ], [ pi.endStubX, midy ] ] 13027 }, 13028 linesToEnd = { 13029 x: [ [ pi.endStubX, pi.startStubY ] ], 13030 y: [ [ pi.startStubX, pi.endStubY ] ] 13031 }, 13032 startToEnd = { 13033 x: [ [ pi.startStubX, pi.endStubY ], [ pi.endStubX, pi.endStubY ] ], 13034 y: [ [ pi.endStubX, pi.startStubY ], [ pi.endStubX, pi.endStubY ] ] 13035 }, 13036 startToMidToEnd = { 13037 x: [ [ pi.startStubX, midy ], [ pi.endStubX, midy ], [ pi.endStubX, pi.endStubY ] ], 13038 y: [ [ midx, pi.startStubY ], [ midx, pi.endStubY ], [ pi.endStubX, pi.endStubY ] ] 13039 }, 13040 otherStubs = { 13041 x: [ pi.startStubY, pi.endStubY ], 13042 y: [ pi.startStubX, pi.endStubX ] 13043 }, 13044 soIdx = orientations[axis][0], toIdx = orientations[axis][1], 13045 _so = pi.so[soIdx] + 1, 13046 _to = pi.to[toIdx] + 1, 13047 otherFlipped = (pi.to[toIdx] === -1 && (otherStubs[axis][1] < otherStubs[axis][0])) || (pi.to[toIdx] === 1 && (otherStubs[axis][1] > otherStubs[axis][0])), 13048 stub1 = stubs[axis][_so][0], 13049 stub2 = stubs[axis][_so][1], 13050 segmentIndexes = sis[axis][_so][_to]; 13051 13052 if (pi.segment === segmentIndexes[3] || (pi.segment === segmentIndexes[2] && otherFlipped)) { 13053 return midLines[axis]; 13054 } 13055 else if (pi.segment === segmentIndexes[2] && stub2 < stub1) { 13056 return linesToEnd[axis]; 13057 } 13058 else if ((pi.segment === segmentIndexes[2] && stub2 >= stub1) || (pi.segment === segmentIndexes[1] && !otherFlipped)) { 13059 return startToMidToEnd[axis]; 13060 } 13061 else if (pi.segment === segmentIndexes[0] || (pi.segment === segmentIndexes[1] && otherFlipped)) { 13062 return startToEnd[axis]; 13063 } 13064 }, 13065 orthogonal: function (axis, startStub, otherStartStub, endStub, otherEndStub) { 13066 var pi = paintInfo, 13067 extent = { 13068 "x": pi.so[0] === -1 ? Math.min(startStub, endStub) : Math.max(startStub, endStub), 13069 "y": pi.so[1] === -1 ? Math.min(startStub, endStub) : Math.max(startStub, endStub) 13070 }[axis]; 13071 13072 return { 13073 "x": [ 13074 [ extent, otherStartStub ], 13075 [ extent, otherEndStub ], 13076 [ endStub, otherEndStub ] 13077 ], 13078 "y": [ 13079 [ otherStartStub, extent ], 13080 [ otherEndStub, extent ], 13081 [ otherEndStub, endStub ] 13082 ] 13083 }[axis]; 13084 }, 13085 opposite: function (axis, ss, oss, es) { 13086 var pi = paintInfo, 13087 otherAxis = {"x": "y", "y": "x"}[axis], 13088 dim = {"x": "height", "y": "width"}[axis], 13089 comparator = pi["is" + axis.toUpperCase() + "GreaterThanStubTimes2"]; 13090 13091 if (params.sourceEndpoint.elementId === params.targetEndpoint.elementId) { 13092 var _val = oss + ((1 - params.sourceEndpoint.anchor[otherAxis]) * params.sourceInfo[dim]) + _super.maxStub; 13093 return { 13094 "x": [ 13095 [ ss, _val ], 13096 [ es, _val ] 13097 ], 13098 "y": [ 13099 [ _val, ss ], 13100 [ _val, es ] 13101 ] 13102 }[axis]; 13103 13104 } 13105 else if (!comparator || (pi.so[idx] === 1 && ss > es) || (pi.so[idx] === -1 && ss < es)) { 13106 return { 13107 "x": [ 13108 [ ss, midy ], 13109 [ es, midy ] 13110 ], 13111 "y": [ 13112 [ midx, ss ], 13113 [ midx, es ] 13114 ] 13115 }[axis]; 13116 } 13117 else if ((pi.so[idx] === 1 && ss < es) || (pi.so[idx] === -1 && ss > es)) { 13118 return { 13119 "x": [ 13120 [ midx, pi.sy ], 13121 [ midx, pi.ty ] 13122 ], 13123 "y": [ 13124 [ pi.sx, midy ], 13125 [ pi.tx, midy ] 13126 ] 13127 }[axis]; 13128 } 13129 } 13130 }; 13131 13132 // compute the rest of the line 13133 var p = lineCalculators[paintInfo.anchorOrientation](paintInfo.sourceAxis, ss, oss, es, oes); 13134 if (p) { 13135 for (var i = 0; i < p.length; i++) { 13136 addSegment(segments, p[i][0], p[i][1], paintInfo); 13137 } 13138 } 13139 13140 // line to end stub 13141 addSegment(segments, stubs[2], stubs[3], paintInfo); 13142 13143 } 13144 13145 // end stub to end (common) 13146 addSegment(segments, paintInfo.tx, paintInfo.ty, paintInfo); 13147 13148 // write out the segments. 13149 writeSegments(this, segments, paintInfo); 13150 }; 13151 13152 /*this.getPath = function () { 13153 var _last = null, _lastAxis = null, s = [], segs = segments; 13154 for (var i = 0; i < segs.length; i++) { 13155 var seg = segs[i], axis = seg[4], axisIndex = (axis == "v" ? 3 : 2); 13156 if (_last != null && _lastAxis === axis) { 13157 _last[axisIndex] = seg[axisIndex]; 13158 } 13159 else { 13160 if (seg[0] != seg[2] || seg[1] != seg[3]) { 13161 s.push({ 13162 start: [ seg[0], seg[1] ], 13163 end: [ seg[2], seg[3] ] 13164 }); 13165 _last = seg; 13166 _lastAxis = seg[4]; 13167 } 13168 } 13169 } 13170 return s; 13171 };*/ 13172 }; 13173 13174 _ju.extend(Flowchart, _jp.Connectors.AbstractConnector); 13175 _jp.registerConnectorType(Flowchart, "Flowchart"); 13176 }).call(typeof window !== 'undefined' ? window : this); 13177 /* 13178 * jsPlumb Community Edition 13179 * 13180 * Provides a way to visually connect elements on an HTML page, using SVG. 13181 * 13182 * This file contains the code for the Bezier connector type. 13183 * 13184 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 13185 * 13186 * https://jsplumbtoolkit.com 13187 * https://github.com/jsplumb/jsplumb 13188 * 13189 * Dual licensed under the MIT and GPL2 licenses. 13190 */ 13191 ; 13192 (function () { 13193 13194 "use strict"; 13195 var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil; 13196 13197 _jp.Connectors.AbstractBezierConnector = function(params) { 13198 params = params || {}; 13199 var showLoopback = params.showLoopback !== false, 13200 curviness = params.curviness || 10, 13201 margin = params.margin || 5, 13202 proximityLimit = params.proximityLimit || 80, 13203 clockwise = params.orientation && params.orientation === "clockwise", 13204 loopbackRadius = params.loopbackRadius || 25, 13205 isLoopbackCurrently = false, 13206 _super; 13207 13208 this.overrideSetEditable = function() { return !isLoopbackCurrently; }; 13209 13210 this._compute = function (paintInfo, p) { 13211 13212 var sp = p.sourcePos, 13213 tp = p.targetPos, 13214 _w = Math.abs(sp[0] - tp[0]), 13215 _h = Math.abs(sp[1] - tp[1]); 13216 13217 if (!showLoopback || (p.sourceEndpoint.elementId !== p.targetEndpoint.elementId)) { 13218 isLoopbackCurrently = false; 13219 this._computeBezier(paintInfo, p, sp, tp, _w, _h); 13220 } else { 13221 isLoopbackCurrently = true; 13222 // a loopback connector. draw an arc from one anchor to the other. 13223 var x1 = p.sourcePos[0], y1 = p.sourcePos[1] - margin, 13224 cx = x1, cy = y1 - loopbackRadius, 13225 // canvas sizing stuff, to ensure the whole painted area is visible. 13226 _x = cx - loopbackRadius, 13227 _y = cy - loopbackRadius; 13228 13229 _w = 2 * loopbackRadius; 13230 _h = 2 * loopbackRadius; 13231 13232 paintInfo.points[0] = _x; 13233 paintInfo.points[1] = _y; 13234 paintInfo.points[2] = _w; 13235 paintInfo.points[3] = _h; 13236 13237 // ADD AN ARC SEGMENT. 13238 _super.addSegment(this, "Arc", { 13239 loopback: true, 13240 x1: (x1 - _x) + 4, 13241 y1: y1 - _y, 13242 startAngle: 0, 13243 endAngle: 2 * Math.PI, 13244 r: loopbackRadius, 13245 ac: !clockwise, 13246 x2: (x1 - _x) - 4, 13247 y2: y1 - _y, 13248 cx: cx - _x, 13249 cy: cy - _y 13250 }); 13251 } 13252 }; 13253 13254 _super = _jp.Connectors.AbstractConnector.apply(this, arguments); 13255 return _super; 13256 }; 13257 _ju.extend(_jp.Connectors.AbstractBezierConnector, _jp.Connectors.AbstractConnector); 13258 13259 var Bezier = function (params) { 13260 params = params || {}; 13261 this.type = "Bezier"; 13262 13263 var _super = _jp.Connectors.AbstractBezierConnector.apply(this, arguments), 13264 majorAnchor = params.curviness || 150, 13265 minorAnchor = 10; 13266 13267 this.getCurviness = function () { 13268 return majorAnchor; 13269 }; 13270 13271 this._findControlPoint = function (point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, soo, too) { 13272 // determine if the two anchors are perpendicular to each other in their orientation. we swap the control 13273 // points around if so (code could be tightened up) 13274 var perpendicular = soo[0] !== too[0] || soo[1] === too[1], 13275 p = []; 13276 13277 if (!perpendicular) { 13278 if (soo[0] === 0) { 13279 p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor); 13280 } 13281 else { 13282 p.push(point[0] - (majorAnchor * soo[0])); 13283 } 13284 13285 if (soo[1] === 0) { 13286 p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor); 13287 } 13288 else { 13289 p.push(point[1] + (majorAnchor * too[1])); 13290 } 13291 } 13292 else { 13293 if (too[0] === 0) { 13294 p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor); 13295 } 13296 else { 13297 p.push(point[0] + (majorAnchor * too[0])); 13298 } 13299 13300 if (too[1] === 0) { 13301 p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor); 13302 } 13303 else { 13304 p.push(point[1] + (majorAnchor * soo[1])); 13305 } 13306 } 13307 13308 return p; 13309 }; 13310 13311 this._computeBezier = function (paintInfo, p, sp, tp, _w, _h) { 13312 13313 var geometry = this.getGeometry(), _CP, _CP2, 13314 _sx = sp[0] < tp[0] ? _w : 0, 13315 _sy = sp[1] < tp[1] ? _h : 0, 13316 _tx = sp[0] < tp[0] ? 0 : _w, 13317 _ty = sp[1] < tp[1] ? 0 : _h; 13318 13319 if ((this.hasBeenEdited() || this.isEditing()) && geometry != null && geometry.controlPoints != null && geometry.controlPoints[0] != null && geometry.controlPoints[1] != null) { 13320 _CP = geometry.controlPoints[0]; 13321 _CP2 = geometry.controlPoints[1]; 13322 } 13323 else { 13324 _CP = this._findControlPoint([_sx, _sy], sp, tp, p.sourceEndpoint, p.targetEndpoint, paintInfo.so, paintInfo.to); 13325 _CP2 = this._findControlPoint([_tx, _ty], tp, sp, p.targetEndpoint, p.sourceEndpoint, paintInfo.to, paintInfo.so); 13326 } 13327 13328 _super.setGeometry({controlPoints:[_CP, _CP2]}, true); 13329 13330 _super.addSegment(this, "Bezier", { 13331 x1: _sx, y1: _sy, x2: _tx, y2: _ty, 13332 cp1x: _CP[0], cp1y: _CP[1], cp2x: _CP2[0], cp2y: _CP2[1] 13333 }); 13334 }; 13335 13336 13337 }; 13338 13339 _ju.extend(Bezier, _jp.Connectors.AbstractBezierConnector); 13340 _jp.registerConnectorType(Bezier, "Bezier"); 13341 13342 }).call(typeof window !== 'undefined' ? window : this); 13343 /* 13344 * jsPlumb Community Edition 13345 * 13346 * Provides a way to visually connect elements on an HTML page, using SVG. 13347 * 13348 * This file contains the state machine connectors, which extend AbstractBezierConnector. 13349 * 13350 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 13351 * 13352 * https://jsplumbtoolkit.com 13353 * https://github.com/jsplumb/jsplumb 13354 * 13355 * Dual licensed under the MIT and GPL2 licenses. 13356 */ 13357 ; 13358 (function () { 13359 13360 "use strict"; 13361 var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil; 13362 13363 var _segment = function (x1, y1, x2, y2) { 13364 if (x1 <= x2 && y2 <= y1) { 13365 return 1; 13366 } 13367 else if (x1 <= x2 && y1 <= y2) { 13368 return 2; 13369 } 13370 else if (x2 <= x1 && y2 >= y1) { 13371 return 3; 13372 } 13373 return 4; 13374 }, 13375 13376 // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the 13377 // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they 13378 // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the 13379 // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and 13380 // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element 13381 // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. 13382 // 13383 // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: 13384 // 13385 // 0 - absolute x 13386 // 1 - absolute y 13387 // 2 - proportional x in element (0 is left edge, 1 is right edge) 13388 // 3 - proportional y in element (0 is top edge, 1 is bottom edge) 13389 // 13390 _findControlPoint = function (midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { 13391 // TODO (maybe) 13392 // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. 13393 if (distance <= proximityLimit) { 13394 return [midx, midy]; 13395 } 13396 13397 if (segment === 1) { 13398 if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) { 13399 return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; 13400 } 13401 else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) { 13402 return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; 13403 } 13404 else { 13405 return [ midx + (-1 * dx) , midy + (-1 * dy) ]; 13406 } 13407 } 13408 else if (segment === 2) { 13409 if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) { 13410 return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; 13411 } 13412 else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) { 13413 return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; 13414 } 13415 else { 13416 return [ midx + dx, midy + (-1 * dy) ]; 13417 } 13418 } 13419 else if (segment === 3) { 13420 if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) { 13421 return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; 13422 } 13423 else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) { 13424 return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; 13425 } 13426 else { 13427 return [ midx + (-1 * dx) , midy + (-1 * dy) ]; 13428 } 13429 } 13430 else if (segment === 4) { 13431 if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) { 13432 return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; 13433 } 13434 else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) { 13435 return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; 13436 } 13437 else { 13438 return [ midx + dx , midy + (-1 * dy) ]; 13439 } 13440 } 13441 13442 }; 13443 13444 var StateMachine = function (params) { 13445 params = params || {}; 13446 this.type = "StateMachine"; 13447 13448 var _super = _jp.Connectors.AbstractBezierConnector.apply(this, arguments), 13449 curviness = params.curviness || 10, 13450 margin = params.margin || 5, 13451 proximityLimit = params.proximityLimit || 80, 13452 clockwise = params.orientation && params.orientation === "clockwise", 13453 _controlPoint; 13454 13455 this._computeBezier = function(paintInfo, params, sp, tp, w, h) { 13456 var _sx = params.sourcePos[0] < params.targetPos[0] ? 0 : w, 13457 _sy = params.sourcePos[1] < params.targetPos[1] ? 0 : h, 13458 _tx = params.sourcePos[0] < params.targetPos[0] ? w : 0, 13459 _ty = params.sourcePos[1] < params.targetPos[1] ? h : 0; 13460 13461 // now adjust for the margin 13462 if (params.sourcePos[2] === 0) { 13463 _sx -= margin; 13464 } 13465 if (params.sourcePos[2] === 1) { 13466 _sx += margin; 13467 } 13468 if (params.sourcePos[3] === 0) { 13469 _sy -= margin; 13470 } 13471 if (params.sourcePos[3] === 1) { 13472 _sy += margin; 13473 } 13474 if (params.targetPos[2] === 0) { 13475 _tx -= margin; 13476 } 13477 if (params.targetPos[2] === 1) { 13478 _tx += margin; 13479 } 13480 if (params.targetPos[3] === 0) { 13481 _ty -= margin; 13482 } 13483 if (params.targetPos[3] === 1) { 13484 _ty += margin; 13485 } 13486 13487 // 13488 // these connectors are quadratic bezier curves, having a single control point. if both anchors 13489 // are located at 0.5 on their respective faces, the control point is set to the midpoint and you 13490 // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since 13491 // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned 13492 // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. 13493 // 13494 // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes 13495 // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, 13496 // for example, we might increase the distance the control point is away from the midpoint in a bid to 13497 // steer it around that node. this will work within limits, but i think those limits would also be the likely 13498 // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. 13499 // 13500 // the second possible change is actually two possible changes: firstly, it is possible we should gradually 13501 // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some 13502 // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors 13503 // with respect to how far their anchor is from the center of its respective face. this could either look cool, 13504 // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. 13505 // 13506 13507 var _midx = (_sx + _tx) / 2, 13508 _midy = (_sy + _ty) / 2, 13509 segment = _segment(_sx, _sy, _tx, _ty), 13510 distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)), 13511 cp1x, cp2x, cp1y, cp2y, 13512 geometry = _super.getGeometry(); 13513 13514 if ((this.hasBeenEdited() || this.isEditing()) && geometry != null) { 13515 cp1x = geometry.controlPoints[0][0]; 13516 cp1y = geometry.controlPoints[0][1]; 13517 cp2x = geometry.controlPoints[1][0]; 13518 cp2y = geometry.controlPoints[1][1]; 13519 } 13520 else { 13521 // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it 13522 // will work by extending the control point to force the curve to be, um, curvier. 13523 _controlPoint = _findControlPoint(_midx, 13524 _midy, 13525 segment, 13526 params.sourcePos, 13527 params.targetPos, 13528 curviness, curviness, 13529 distance, 13530 proximityLimit); 13531 13532 cp1x = _controlPoint[0]; 13533 cp2x = _controlPoint[0]; 13534 cp1y = _controlPoint[1]; 13535 cp2y = _controlPoint[1]; 13536 13537 _super.setGeometry({controlPoints:[_controlPoint, _controlPoint]}, true); 13538 } 13539 13540 _super.addSegment(this, "Bezier", { 13541 x1: _tx, y1: _ty, x2: _sx, y2: _sy, 13542 cp1x: cp1x, cp1y: cp1y, 13543 cp2x: cp2x, cp2y: cp2y 13544 }); 13545 }; 13546 }; 13547 13548 _ju.extend(StateMachine, _jp.Connectors.AbstractBezierConnector); 13549 _jp.registerConnectorType(StateMachine, "StateMachine"); 13550 13551 }).call(typeof window !== 'undefined' ? window : this); 13552 /* 13553 * jsPlumb Community Edition 13554 * 13555 * Provides a way to visually connect elements on an HTML page, using SVG. 13556 * 13557 * This file contains the 'flowchart' connectors, consisting of vertical and horizontal line segments. 13558 * 13559 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 13560 * 13561 * https://jsplumbtoolkit.com 13562 * https://github.com/jsplumb/jsplumb 13563 * 13564 * Dual licensed under the MIT and GPL2 licenses. 13565 */ 13566 ; 13567 (function () { 13568 13569 "use strict"; 13570 var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil; 13571 var STRAIGHT = "Straight"; 13572 13573 var Straight = function (params) { 13574 this.type = STRAIGHT; 13575 var _super = _jp.Connectors.AbstractConnector.apply(this, arguments); 13576 13577 this._compute = function (paintInfo, _) { 13578 _super.addSegment(this, STRAIGHT, {x1: paintInfo.sx, y1: paintInfo.sy, x2: paintInfo.startStubX, y2: paintInfo.startStubY}); 13579 _super.addSegment(this, STRAIGHT, {x1: paintInfo.startStubX, y1: paintInfo.startStubY, x2: paintInfo.endStubX, y2: paintInfo.endStubY}); 13580 _super.addSegment(this, STRAIGHT, {x1: paintInfo.endStubX, y1: paintInfo.endStubY, x2: paintInfo.tx, y2: paintInfo.ty}); 13581 }; 13582 }; 13583 13584 _ju.extend(Straight, _jp.Connectors.AbstractConnector); 13585 _jp.registerConnectorType(Straight, STRAIGHT); 13586 13587 }).call(typeof window !== 'undefined' ? window : this); 13588 /* 13589 * jsPlumb Community Edition 13590 * 13591 * Provides a way to visually connect elements on an HTML page, using SVG. 13592 * 13593 * This file contains the SVG renderers. 13594 * 13595 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 13596 * 13597 * https://jsplumbtoolkit.com 13598 * https://github.com/jsplumb/jsplumb 13599 * 13600 * Dual licensed under the MIT and GPL2 licenses. 13601 */ 13602 ; 13603 (function () { 13604 13605 // ************************** SVG utility methods ******************************************** 13606 13607 "use strict"; 13608 var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil; 13609 13610 var svgAttributeMap = { 13611 "stroke-linejoin": "stroke-linejoin", 13612 "stroke-dashoffset": "stroke-dashoffset", 13613 "stroke-linecap": "stroke-linecap" 13614 }, 13615 STROKE_DASHARRAY = "stroke-dasharray", 13616 DASHSTYLE = "dashstyle", 13617 LINEAR_GRADIENT = "linearGradient", 13618 RADIAL_GRADIENT = "radialGradient", 13619 DEFS = "defs", 13620 FILL = "fill", 13621 STOP = "stop", 13622 STROKE = "stroke", 13623 STROKE_WIDTH = "stroke-width", 13624 STYLE = "style", 13625 NONE = "none", 13626 JSPLUMB_GRADIENT = "jsplumb_gradient_", 13627 LINE_WIDTH = "strokeWidth", 13628 ns = { 13629 svg: "http://www.w3.org/2000/svg" 13630 }, 13631 _attr = function (node, attributes) { 13632 for (var i in attributes) { 13633 node.setAttribute(i, "" + attributes[i]); 13634 } 13635 }, 13636 _node = function (name, attributes) { 13637 attributes = attributes || {}; 13638 attributes.version = "1.1"; 13639 attributes.xmlns = ns.svg; 13640 return _jp.createElementNS(ns.svg, name, null, null, attributes); 13641 }, 13642 _pos = function (d) { 13643 return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; 13644 }, 13645 _clearGradient = function (parent) { 13646 var els = parent.querySelectorAll(" defs,linearGradient,radialGradient"); 13647 for (var i = 0; i < els.length; i++) { 13648 els[i].parentNode.removeChild(els[i]); 13649 } 13650 }, 13651 _updateGradient = function (parent, node, style, dimensions, uiComponent) { 13652 var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.instance.idstamp(); 13653 // first clear out any existing gradient 13654 _clearGradient(parent); 13655 // this checks for an 'offset' property in the gradient, and in the absence of it, assumes 13656 // we want a linear gradient. if it's there, we create a radial gradient. 13657 // it is possible that a more explicit means of defining the gradient type would be 13658 // better. relying on 'offset' means that we can never have a radial gradient that uses 13659 // some default offset, for instance. 13660 // issue 244 suggested the 'gradientUnits' attribute; without this, straight/flowchart connectors with gradients would 13661 // not show gradients when the line was perfectly horizontal or vertical. 13662 var g; 13663 if (!style.gradient.offset) { 13664 g = _node(LINEAR_GRADIENT, {id: id, gradientUnits: "userSpaceOnUse"}); 13665 } 13666 else { 13667 g = _node(RADIAL_GRADIENT, { id: id }); 13668 } 13669 13670 var defs = _node(DEFS); 13671 parent.appendChild(defs); 13672 defs.appendChild(g); 13673 13674 // the svg radial gradient seems to treat stops in the reverse 13675 // order to how canvas does it. so we want to keep all the maths the same, but 13676 // iterate the actual style declarations in reverse order, if the x indexes are not in order. 13677 for (var i = 0; i < style.gradient.stops.length; i++) { 13678 var styleToUse = uiComponent.segment === 1 || uiComponent.segment === 2 ? i : style.gradient.stops.length - 1 - i, 13679 stopColor = style.gradient.stops[styleToUse][1], 13680 s = _node(STOP, {"offset": Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color": stopColor}); 13681 13682 g.appendChild(s); 13683 } 13684 var applyGradientTo = style.stroke ? STROKE : FILL; 13685 node.setAttribute(applyGradientTo, "url(#" + id + ")"); 13686 }, 13687 _applyStyles = function (parent, node, style, dimensions, uiComponent) { 13688 13689 node.setAttribute(FILL, style.fill ? style.fill : NONE); 13690 node.setAttribute(STROKE, style.stroke ? style.stroke : NONE); 13691 13692 if (style.gradient) { 13693 _updateGradient(parent, node, style, dimensions, uiComponent); 13694 } 13695 else { 13696 // make sure we clear any existing gradient 13697 _clearGradient(parent); 13698 node.setAttribute(STYLE, ""); 13699 } 13700 13701 if (style.strokeWidth) { 13702 node.setAttribute(STROKE_WIDTH, style.strokeWidth); 13703 } 13704 13705 // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like 13706 // the syntax in VML but is actually kind of nasty: values are given in the pixel 13707 // coordinate space, whereas in VML they are multiples of the width of the stroked 13708 // line, which makes a lot more sense. for that reason, jsPlumb is supporting both 13709 // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from 13710 // VML, which will be the preferred method. the code below this converts a dashstyle 13711 // attribute given in terms of stroke width into a pixel representation, by using the 13712 // stroke's lineWidth. 13713 if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { 13714 var sep = style[DASHSTYLE].indexOf(",") === -1 ? " " : ",", 13715 parts = style[DASHSTYLE].split(sep), 13716 styleToUse = ""; 13717 parts.forEach(function (p) { 13718 styleToUse += (Math.floor(p * style.strokeWidth) + sep); 13719 }); 13720 node.setAttribute(STROKE_DASHARRAY, styleToUse); 13721 } 13722 else if (style[STROKE_DASHARRAY]) { 13723 node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); 13724 } 13725 13726 // extra attributes such as join type, dash offset. 13727 for (var i in svgAttributeMap) { 13728 if (style[i]) { 13729 node.setAttribute(svgAttributeMap[i], style[i]); 13730 } 13731 } 13732 }, 13733 _appendAtIndex = function (svg, path, idx) { 13734 if (svg.childNodes.length > idx) { 13735 svg.insertBefore(path, svg.childNodes[idx]); 13736 } 13737 else { 13738 svg.appendChild(path); 13739 } 13740 }; 13741 13742 /** 13743 utility methods for other objects to use. 13744 */ 13745 _ju.svg = { 13746 node: _node, 13747 attr: _attr, 13748 pos: _pos 13749 }; 13750 13751 // ************************** / SVG utility methods ******************************************** 13752 13753 /* 13754 * Base class for SVG components. 13755 */ 13756 var SvgComponent = function (params) { 13757 var pointerEventsSpec = params.pointerEventsSpec || "all", renderer = {}; 13758 13759 _jp.jsPlumbUIComponent.apply(this, params.originalArgs); 13760 this.canvas = null; 13761 this.path = null; 13762 this.svg = null; 13763 this.bgCanvas = null; 13764 13765 var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), 13766 svgParams = { 13767 "style": "", 13768 "width": 0, 13769 "height": 0, 13770 "pointer-events": pointerEventsSpec, 13771 "position": "absolute" 13772 }; 13773 13774 this.svg = _node("svg", svgParams); 13775 13776 if (params.useDivWrapper) { 13777 this.canvas = _jp.createElement("div", { position : "absolute" }); 13778 _ju.sizeElement(this.canvas, 0, 0, 1, 1); 13779 this.canvas.className = clazz; 13780 } 13781 else { 13782 _attr(this.svg, { "class": clazz }); 13783 this.canvas = this.svg; 13784 } 13785 13786 params._jsPlumb.appendElement(this.canvas, params.originalArgs[0].parent); 13787 if (params.useDivWrapper) { 13788 this.canvas.appendChild(this.svg); 13789 } 13790 13791 var displayElements = [ this.canvas ]; 13792 this.getDisplayElements = function () { 13793 return displayElements; 13794 }; 13795 13796 this.appendDisplayElement = function (el) { 13797 displayElements.push(el); 13798 }; 13799 13800 this.paint = function (style, anchor, extents) { 13801 if (style != null) { 13802 13803 var xy = [ this.x, this.y ], wh = [ this.w, this.h ], p; 13804 if (extents != null) { 13805 if (extents.xmin < 0) { 13806 xy[0] += extents.xmin; 13807 } 13808 if (extents.ymin < 0) { 13809 xy[1] += extents.ymin; 13810 } 13811 wh[0] = extents.xmax + ((extents.xmin < 0) ? -extents.xmin : 0); 13812 wh[1] = extents.ymax + ((extents.ymin < 0) ? -extents.ymin : 0); 13813 } 13814 13815 if (params.useDivWrapper) { 13816 _ju.sizeElement(this.canvas, xy[0], xy[1], wh[0], wh[1]); 13817 xy[0] = 0; 13818 xy[1] = 0; 13819 p = _pos([ 0, 0 ]); 13820 } 13821 else { 13822 p = _pos([ xy[0], xy[1] ]); 13823 } 13824 13825 renderer.paint.apply(this, arguments); 13826 13827 _attr(this.svg, { 13828 "style": p, 13829 "width": wh[0] || 0, 13830 "height": wh[1] || 0 13831 }); 13832 } 13833 }; 13834 13835 return { 13836 renderer: renderer 13837 }; 13838 }; 13839 13840 _ju.extend(SvgComponent, _jp.jsPlumbUIComponent, { 13841 cleanup: function (force) { 13842 if (force || this.typeId == null) { 13843 if (this.canvas) { 13844 this.canvas._jsPlumb = null; 13845 } 13846 if (this.svg) { 13847 this.svg._jsPlumb = null; 13848 } 13849 if (this.bgCanvas) { 13850 this.bgCanvas._jsPlumb = null; 13851 } 13852 13853 if (this.canvas && this.canvas.parentNode) { 13854 this.canvas.parentNode.removeChild(this.canvas); 13855 } 13856 if (this.bgCanvas && this.bgCanvas.parentNode) { 13857 this.canvas.parentNode.removeChild(this.canvas); 13858 } 13859 13860 this.svg = null; 13861 this.canvas = null; 13862 this.path = null; 13863 this.group = null; 13864 } 13865 else { 13866 // if not a forced cleanup, just detach from DOM for now. 13867 if (this.canvas && this.canvas.parentNode) { 13868 this.canvas.parentNode.removeChild(this.canvas); 13869 } 13870 if (this.bgCanvas && this.bgCanvas.parentNode) { 13871 this.bgCanvas.parentNode.removeChild(this.bgCanvas); 13872 } 13873 } 13874 }, 13875 reattach:function(instance) { 13876 var c = instance.getContainer(); 13877 if (this.canvas && this.canvas.parentNode == null) { 13878 c.appendChild(this.canvas); 13879 } 13880 if (this.bgCanvas && this.bgCanvas.parentNode == null) { 13881 c.appendChild(this.bgCanvas); 13882 } 13883 }, 13884 setVisible: function (v) { 13885 if (this.canvas) { 13886 this.canvas.style.display = v ? "block" : "none"; 13887 } 13888 } 13889 }); 13890 13891 /* 13892 * Base class for SVG connectors. 13893 */ 13894 _jp.ConnectorRenderers.svg = function (params) { 13895 var self = this, 13896 _super = SvgComponent.apply(this, [ 13897 { 13898 cssClass: params._jsPlumb.connectorClass + (this.isEditable() ? " " + params._jsPlumb.editableConnectorClass : ""), 13899 originalArgs: arguments, 13900 pointerEventsSpec: "none", 13901 _jsPlumb: params._jsPlumb 13902 } 13903 ]); 13904 13905 var _superSetEditable = this.setEditable; 13906 this.setEditable = function(e) { 13907 var result = _superSetEditable.apply(this, [e]); 13908 _jp[result ? "addClass" : "removeClass"](this.canvas, this._jsPlumb.instance.editableConnectorClass); 13909 }; 13910 13911 _super.renderer.paint = function (style, anchor, extents) { 13912 13913 var segments = self.getSegments(), p = "", offset = [0, 0]; 13914 if (extents.xmin < 0) { 13915 offset[0] = -extents.xmin; 13916 } 13917 if (extents.ymin < 0) { 13918 offset[1] = -extents.ymin; 13919 } 13920 13921 if (segments.length > 0) { 13922 13923 p = self.getPathData(); 13924 13925 var a = { 13926 d: p, 13927 transform: "translate(" + offset[0] + "," + offset[1] + ")", 13928 "pointer-events": params["pointer-events"] || "visibleStroke" 13929 }, 13930 outlineStyle = null, 13931 d = [self.x, self.y, self.w, self.h]; 13932 13933 // outline style. actually means drawing an svg object underneath the main one. 13934 if (style.outlineStroke) { 13935 var outlineWidth = style.outlineWidth || 1, 13936 outlineStrokeWidth = style.strokeWidth + (2 * outlineWidth); 13937 outlineStyle = _jp.extend({}, style); 13938 delete outlineStyle.gradient; 13939 outlineStyle.stroke = style.outlineStroke; 13940 outlineStyle.strokeWidth = outlineStrokeWidth; 13941 13942 if (self.bgPath == null) { 13943 self.bgPath = _node("path", a); 13944 _jp.addClass(self.bgPath, _jp.connectorOutlineClass); 13945 _appendAtIndex(self.svg, self.bgPath, 0); 13946 } 13947 else { 13948 _attr(self.bgPath, a); 13949 } 13950 13951 _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); 13952 } 13953 13954 if (self.path == null) { 13955 self.path = _node("path", a); 13956 _appendAtIndex(self.svg, self.path, style.outlineStroke ? 1 : 0); 13957 } 13958 else { 13959 _attr(self.path, a); 13960 } 13961 13962 _applyStyles(self.svg, self.path, style, d, self); 13963 } 13964 }; 13965 }; 13966 _ju.extend(_jp.ConnectorRenderers.svg, SvgComponent); 13967 13968 // ******************************* svg segment renderer ***************************************************** 13969 13970 13971 // ******************************* /svg segments ***************************************************** 13972 13973 /* 13974 * Base class for SVG endpoints. 13975 */ 13976 var SvgEndpoint = _jp.SvgEndpoint = function (params) { 13977 var _super = SvgComponent.apply(this, [ 13978 { 13979 cssClass: params._jsPlumb.endpointClass, 13980 originalArgs: arguments, 13981 pointerEventsSpec: "all", 13982 useDivWrapper: true, 13983 _jsPlumb: params._jsPlumb 13984 } 13985 ]); 13986 13987 _super.renderer.paint = function (style) { 13988 var s = _jp.extend({}, style); 13989 if (s.outlineStroke) { 13990 s.stroke = s.outlineStroke; 13991 } 13992 13993 if (this.node == null) { 13994 this.node = this.makeNode(s); 13995 this.svg.appendChild(this.node); 13996 } 13997 else if (this.updateNode != null) { 13998 this.updateNode(this.node); 13999 } 14000 _applyStyles(this.svg, this.node, s, [ this.x, this.y, this.w, this.h ], this); 14001 _pos(this.node, [ this.x, this.y ]); 14002 }.bind(this); 14003 14004 }; 14005 _ju.extend(SvgEndpoint, SvgComponent); 14006 14007 /* 14008 * SVG Dot Endpoint 14009 */ 14010 _jp.Endpoints.svg.Dot = function () { 14011 _jp.Endpoints.Dot.apply(this, arguments); 14012 SvgEndpoint.apply(this, arguments); 14013 this.makeNode = function (style) { 14014 return _node("circle", { 14015 "cx": this.w / 2, 14016 "cy": this.h / 2, 14017 "r": this.radius 14018 }); 14019 }; 14020 this.updateNode = function (node) { 14021 _attr(node, { 14022 "cx": this.w / 2, 14023 "cy": this.h / 2, 14024 "r": this.radius 14025 }); 14026 }; 14027 }; 14028 _ju.extend(_jp.Endpoints.svg.Dot, [_jp.Endpoints.Dot, SvgEndpoint]); 14029 14030 /* 14031 * SVG Rectangle Endpoint 14032 */ 14033 _jp.Endpoints.svg.Rectangle = function () { 14034 _jp.Endpoints.Rectangle.apply(this, arguments); 14035 SvgEndpoint.apply(this, arguments); 14036 this.makeNode = function (style) { 14037 return _node("rect", { 14038 "width": this.w, 14039 "height": this.h 14040 }); 14041 }; 14042 this.updateNode = function (node) { 14043 _attr(node, { 14044 "width": this.w, 14045 "height": this.h 14046 }); 14047 }; 14048 }; 14049 _ju.extend(_jp.Endpoints.svg.Rectangle, [_jp.Endpoints.Rectangle, SvgEndpoint]); 14050 14051 /* 14052 * SVG Image Endpoint is the default image endpoint. 14053 */ 14054 _jp.Endpoints.svg.Image = _jp.Endpoints.Image; 14055 /* 14056 * Blank endpoint in svg renderer is the default Blank endpoint. 14057 */ 14058 _jp.Endpoints.svg.Blank = _jp.Endpoints.Blank; 14059 /* 14060 * Label overlay in svg renderer is the default Label overlay. 14061 */ 14062 _jp.Overlays.svg.Label = _jp.Overlays.Label; 14063 /* 14064 * Custom overlay in svg renderer is the default Custom overlay. 14065 */ 14066 _jp.Overlays.svg.Custom = _jp.Overlays.Custom; 14067 14068 var AbstractSvgArrowOverlay = function (superclass, originalArgs) { 14069 superclass.apply(this, originalArgs); 14070 _jp.jsPlumbUIComponent.apply(this, originalArgs); 14071 this.isAppendedAtTopLevel = false; 14072 var self = this; 14073 this.path = null; 14074 this.paint = function (params, containerExtents) { 14075 // only draws on connections, not endpoints. 14076 if (params.component.svg && containerExtents) { 14077 if (this.path == null) { 14078 this.path = _node("path", { 14079 "pointer-events": "all" 14080 }); 14081 params.component.svg.appendChild(this.path); 14082 if (this.elementCreated) { 14083 this.elementCreated(this.path, params.component); 14084 } 14085 14086 this.canvas = params.component.svg; // for the sake of completeness; this behaves the same as other overlays 14087 } 14088 var clazz = originalArgs && (originalArgs.length === 1) ? (originalArgs[0].cssClass || "") : "", 14089 offset = [0, 0]; 14090 14091 if (containerExtents.xmin < 0) { 14092 offset[0] = -containerExtents.xmin; 14093 } 14094 if (containerExtents.ymin < 0) { 14095 offset[1] = -containerExtents.ymin; 14096 } 14097 14098 _attr(this.path, { 14099 "d": makePath(params.d), 14100 "class": clazz, 14101 stroke: params.stroke ? params.stroke : null, 14102 fill: params.fill ? params.fill : null, 14103 transform: "translate(" + offset[0] + "," + offset[1] + ")" 14104 }); 14105 } 14106 }; 14107 var makePath = function (d) { 14108 return (isNaN(d.cxy.x) || isNaN(d.cxy.y)) ? "" : "M" + d.hxy.x + "," + d.hxy.y + 14109 " L" + d.tail[0].x + "," + d.tail[0].y + 14110 " L" + d.cxy.x + "," + d.cxy.y + 14111 " L" + d.tail[1].x + "," + d.tail[1].y + 14112 " L" + d.hxy.x + "," + d.hxy.y; 14113 }; 14114 this.transfer = function(target) { 14115 if (target.canvas && this.path && this.path.parentNode) { 14116 this.path.parentNode.removeChild(this.path); 14117 target.canvas.appendChild(this.path); 14118 } 14119 }; 14120 }; 14121 _ju.extend(AbstractSvgArrowOverlay, [_jp.jsPlumbUIComponent, _jp.Overlays.AbstractOverlay], { 14122 cleanup: function (force) { 14123 if (this.path != null) { 14124 if (force) { 14125 this._jsPlumb.instance.removeElement(this.path); 14126 } 14127 else { 14128 if (this.path.parentNode) { 14129 this.path.parentNode.removeChild(this.path); 14130 } 14131 } 14132 } 14133 }, 14134 reattach:function(instance) { 14135 if (this.path && this.canvas && this.path.parentNode == null) { 14136 this.canvas.appendChild(this.path); 14137 } 14138 }, 14139 setVisible: function (v) { 14140 if (this.path != null) { 14141 (this.path.style.display = (v ? "block" : "none")); 14142 } 14143 } 14144 }); 14145 14146 _jp.Overlays.svg.Arrow = function () { 14147 AbstractSvgArrowOverlay.apply(this, [_jp.Overlays.Arrow, arguments]); 14148 }; 14149 _ju.extend(_jp.Overlays.svg.Arrow, [ _jp.Overlays.Arrow, AbstractSvgArrowOverlay ]); 14150 14151 _jp.Overlays.svg.PlainArrow = function () { 14152 AbstractSvgArrowOverlay.apply(this, [_jp.Overlays.PlainArrow, arguments]); 14153 }; 14154 _ju.extend(_jp.Overlays.svg.PlainArrow, [ _jp.Overlays.PlainArrow, AbstractSvgArrowOverlay ]); 14155 14156 _jp.Overlays.svg.Diamond = function () { 14157 AbstractSvgArrowOverlay.apply(this, [_jp.Overlays.Diamond, arguments]); 14158 }; 14159 _ju.extend(_jp.Overlays.svg.Diamond, [ _jp.Overlays.Diamond, AbstractSvgArrowOverlay ]); 14160 14161 // a test 14162 _jp.Overlays.svg.GuideLines = function () { 14163 var path = null, self = this, p1_1, p1_2; 14164 _jp.Overlays.GuideLines.apply(this, arguments); 14165 this.paint = function (params, containerExtents) { 14166 if (path == null) { 14167 path = _node("path"); 14168 params.connector.svg.appendChild(path); 14169 self.attachListeners(path, params.connector); 14170 self.attachListeners(path, self); 14171 14172 p1_1 = _node("path"); 14173 params.connector.svg.appendChild(p1_1); 14174 self.attachListeners(p1_1, params.connector); 14175 self.attachListeners(p1_1, self); 14176 14177 p1_2 = _node("path"); 14178 params.connector.svg.appendChild(p1_2); 14179 self.attachListeners(p1_2, params.connector); 14180 self.attachListeners(p1_2, self); 14181 } 14182 14183 var offset = [0, 0]; 14184 if (containerExtents.xmin < 0) { 14185 offset[0] = -containerExtents.xmin; 14186 } 14187 if (containerExtents.ymin < 0) { 14188 offset[1] = -containerExtents.ymin; 14189 } 14190 14191 _attr(path, { 14192 "d": makePath(params.head, params.tail), 14193 stroke: "red", 14194 fill: null, 14195 transform: "translate(" + offset[0] + "," + offset[1] + ")" 14196 }); 14197 14198 _attr(p1_1, { 14199 "d": makePath(params.tailLine[0], params.tailLine[1]), 14200 stroke: "blue", 14201 fill: null, 14202 transform: "translate(" + offset[0] + "," + offset[1] + ")" 14203 }); 14204 14205 _attr(p1_2, { 14206 "d": makePath(params.headLine[0], params.headLine[1]), 14207 stroke: "green", 14208 fill: null, 14209 transform: "translate(" + offset[0] + "," + offset[1] + ")" 14210 }); 14211 }; 14212 14213 var makePath = function (d1, d2) { 14214 return "M " + d1.x + "," + d1.y + 14215 " L" + d2.x + "," + d2.y; 14216 }; 14217 }; 14218 _ju.extend(_jp.Overlays.svg.GuideLines, _jp.Overlays.GuideLines); 14219 }).call(typeof window !== 'undefined' ? window : this); 14220 14221 /* 14222 * jsPlumb Community Edition 14223 * 14224 * Provides a way to visually connect elements on an HTML page, using SVG. 14225 * 14226 * This file contains the 'vanilla' adapter - having no external dependencies other than bundled libs. 14227 * 14228 * Copyright (c) 2010 - 2017 jsPlumb (hello@jsplumbtoolkit.com) 14229 * 14230 * https://jsplumbtoolkit.com 14231 * https://github.com/jsplumb/jsplumb 14232 * 14233 * Dual licensed under the MIT and GPL2 licenses. 14234 */ 14235 ; 14236 (function () { 14237 14238 "use strict"; 14239 var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil, 14240 _jk = root.Katavorio, _jg = root.Biltong; 14241 14242 var _getDragManager = function (instance, category) { 14243 14244 category = category || "main"; 14245 var key = "_katavorio_" + category; 14246 var k = instance[key], 14247 e = instance.getEventManager(); 14248 14249 if (!k) { 14250 k = new _jk({ 14251 bind: e.on, 14252 unbind: e.off, 14253 getSize: _jp.getSize, 14254 getPosition: function (el, relativeToRoot) { 14255 // if this is a nested draggable then compute the offset against its own offsetParent, otherwise 14256 // compute against the Container's origin. see also the getUIPosition method below. 14257 var o = instance.getOffset(el, relativeToRoot, el._katavorioDrag ? el.offsetParent : null); 14258 return [o.left, o.top]; 14259 }, 14260 setPosition: function (el, xy) { 14261 el.style.left = xy[0] + "px"; 14262 el.style.top = xy[1] + "px"; 14263 }, 14264 addClass: _jp.addClass, 14265 removeClass: _jp.removeClass, 14266 intersects: _jg.intersects, 14267 indexOf: function(l, i) { return l.indexOf(i); }, 14268 scope:instance.getDefaultScope(), 14269 css: { 14270 noSelect: instance.dragSelectClass, 14271 droppable: "jtk-droppable", 14272 draggable: "jtk-draggable", 14273 drag: "jtk-drag", 14274 selected: "jtk-drag-selected", 14275 active: "jtk-drag-active", 14276 hover: "jtk-drag-hover", 14277 ghostProxy:"jtk-ghost-proxy" 14278 } 14279 }); 14280 k.setZoom(instance.getZoom()); 14281 instance[key] = k; 14282 instance.bind("zoom", k.setZoom); 14283 } 14284 return k; 14285 }; 14286 14287 var _animProps = function (o, p) { 14288 var _one = function (pName) { 14289 if (p[pName] != null) { 14290 if (_ju.isString(p[pName])) { 14291 var m = p[pName].match(/-=/) ? -1 : 1, 14292 v = p[pName].substring(2); 14293 return o[pName] + (m * v); 14294 } 14295 else { 14296 return p[pName]; 14297 } 14298 } 14299 else { 14300 return o[pName]; 14301 } 14302 }; 14303 return [ _one("left"), _one("top") ]; 14304 }; 14305 14306 _jp.extend(root.jsPlumbInstance.prototype, { 14307 14308 animationSupported:true, 14309 getElement: function (el) { 14310 if (el == null) { 14311 return null; 14312 } 14313 // here we pluck the first entry if el was a list of entries. 14314 // this is not my favourite thing to do, but previous versions of 14315 // jsplumb supported jquery selectors, and it is possible a selector 14316 // will be passed in here. 14317 el = typeof el === "string" ? el : el.length != null && el.enctype == null ? el[0] : el; 14318 return typeof el === "string" ? document.getElementById(el) : el; 14319 }, 14320 removeElement: function (element) { 14321 _getDragManager(this).elementRemoved(element); 14322 this.getEventManager().remove(element); 14323 }, 14324 // 14325 // this adapter supports a rudimentary animation function. no easing is supported. only 14326 // left/top properties are supported. property delta args are expected to be in the form 14327 // 14328 // +=x.xxxx 14329 // 14330 // or 14331 // 14332 // -=x.xxxx 14333 // 14334 doAnimate: function (el, properties, options) { 14335 options = options || {}; 14336 var o = this.getOffset(el), 14337 ap = _animProps(o, properties), 14338 ldist = ap[0] - o.left, 14339 tdist = ap[1] - o.top, 14340 d = options.duration || 250, 14341 step = 15, steps = d / step, 14342 linc = (step / d) * ldist, 14343 tinc = (step / d) * tdist, 14344 idx = 0, 14345 _int = setInterval(function () { 14346 _jp.setPosition(el, { 14347 left: o.left + (linc * (idx + 1)), 14348 top: o.top + (tinc * (idx + 1)) 14349 }); 14350 if (options.step != null) { 14351 options.step(idx, Math.ceil(steps)); 14352 } 14353 idx++; 14354 if (idx >= steps) { 14355 window.clearInterval(_int); 14356 if (options.complete != null) { 14357 options.complete(); 14358 } 14359 } 14360 }, step); 14361 }, 14362 // DRAG/DROP 14363 destroyDraggable: function (el, category) { 14364 _getDragManager(this, category).destroyDraggable(el); 14365 }, 14366 destroyDroppable: function (el, category) { 14367 _getDragManager(this, category).destroyDroppable(el); 14368 }, 14369 initDraggable: function (el, options, category) { 14370 _getDragManager(this, category).draggable(el, options); 14371 }, 14372 initDroppable: function (el, options, category) { 14373 _getDragManager(this, category).droppable(el, options); 14374 }, 14375 isAlreadyDraggable: function (el) { 14376 return el._katavorioDrag != null; 14377 }, 14378 isDragSupported: function (el, options) { 14379 return true; 14380 }, 14381 isDropSupported: function (el, options) { 14382 return true; 14383 }, 14384 isElementDraggable: function (el) { 14385 el = _jp.getElement(el); 14386 return el._katavorioDrag && el._katavorioDrag.isEnabled(); 14387 }, 14388 getDragObject: function (eventArgs) { 14389 return eventArgs[0].drag.getDragElement(); 14390 }, 14391 getDragScope: function (el) { 14392 return el._katavorioDrag && el._katavorioDrag.scopes.join(" ") || ""; 14393 }, 14394 getDropEvent: function (args) { 14395 return args[0].e; 14396 }, 14397 getUIPosition: function (eventArgs, zoom) { 14398 // here the position reported to us by Katavorio is relative to the element's offsetParent. For top 14399 // level nodes that is fine, but if we have a nested draggable then its offsetParent is actually 14400 // not going to be the jsplumb container; it's going to be some child of that element. In that case 14401 // we want to adjust the UI position to account for the offsetParent's position relative to the Container 14402 // origin. 14403 var el = eventArgs[0].el; 14404 if (el.offsetParent == null) { 14405 return null; 14406 } 14407 var finalPos = eventArgs[0].finalPos || eventArgs[0].pos; 14408 var p = { left:finalPos[0], top:finalPos[1] }; 14409 if (el._katavorioDrag && el.offsetParent !== this.getContainer()) { 14410 var oc = this.getOffset(el.offsetParent); 14411 p.left += oc.left; 14412 p.top += oc.top; 14413 } 14414 return p; 14415 }, 14416 setDragFilter: function (el, filter, _exclude) { 14417 if (el._katavorioDrag) { 14418 el._katavorioDrag.setFilter(filter, _exclude); 14419 } 14420 }, 14421 setElementDraggable: function (el, draggable) { 14422 el = _jp.getElement(el); 14423 if (el._katavorioDrag) { 14424 el._katavorioDrag.setEnabled(draggable); 14425 } 14426 }, 14427 setDragScope: function (el, scope) { 14428 if (el._katavorioDrag) { 14429 el._katavorioDrag.k.setDragScope(el, scope); 14430 } 14431 }, 14432 setDropScope:function(el, scope) { 14433 if (el._katavorioDrop && el._katavorioDrop.length > 0) { 14434 el._katavorioDrop[0].k.setDropScope(el, scope); 14435 } 14436 }, 14437 addToPosse:function(el, spec) { 14438 var specs = Array.prototype.slice.call(arguments, 1); 14439 var dm = _getDragManager(this); 14440 _jp.each(el, function(_el) { 14441 _el = [ _jp.getElement(_el) ]; 14442 _el.push.apply(_el, specs ); 14443 dm.addToPosse.apply(dm, _el); 14444 }); 14445 }, 14446 setPosse:function(el, spec) { 14447 var specs = Array.prototype.slice.call(arguments, 1); 14448 var dm = _getDragManager(this); 14449 _jp.each(el, function(_el) { 14450 _el = [ _jp.getElement(_el) ]; 14451 _el.push.apply(_el, specs ); 14452 dm.setPosse.apply(dm, _el); 14453 }); 14454 }, 14455 removeFromPosse:function(el, posseId) { 14456 var specs = Array.prototype.slice.call(arguments, 1); 14457 var dm = _getDragManager(this); 14458 _jp.each(el, function(_el) { 14459 _el = [ _jp.getElement(_el) ]; 14460 _el.push.apply(_el, specs ); 14461 dm.removeFromPosse.apply(dm, _el); 14462 }); 14463 }, 14464 removeFromAllPosses:function(el) { 14465 var dm = _getDragManager(this); 14466 _jp.each(el, function(_el) { dm.removeFromAllPosses(_jp.getElement(_el)); }); 14467 }, 14468 setPosseState:function(el, posseId, state) { 14469 var dm = _getDragManager(this); 14470 _jp.each(el, function(_el) { dm.setPosseState(_jp.getElement(_el), posseId, state); }); 14471 }, 14472 dragEvents: { 14473 'start': 'start', 'stop': 'stop', 'drag': 'drag', 'step': 'step', 14474 'over': 'over', 'out': 'out', 'drop': 'drop', 'complete': 'complete', 14475 'beforeStart':'beforeStart' 14476 }, 14477 animEvents: { 14478 'step': "step", 'complete': 'complete' 14479 }, 14480 stopDrag: function (el) { 14481 if (el._katavorioDrag) { 14482 el._katavorioDrag.abort(); 14483 } 14484 }, 14485 addToDragSelection: function (spec) { 14486 _getDragManager(this).select(spec); 14487 }, 14488 removeFromDragSelection: function (spec) { 14489 _getDragManager(this).deselect(spec); 14490 }, 14491 clearDragSelection: function () { 14492 _getDragManager(this).deselectAll(); 14493 }, 14494 trigger: function (el, event, originalEvent, payload) { 14495 this.getEventManager().trigger(el, event, originalEvent, payload); 14496 }, 14497 doReset:function() { 14498 // look for katavorio instances and reset each one if found. 14499 for (var key in this) { 14500 if (key.indexOf("_katavorio_") === 0) { 14501 this[key].reset(); 14502 } 14503 } 14504 } 14505 }); 14506 14507 var ready = function (f) { 14508 var _do = function () { 14509 if (/complete|loaded|interactive/.test(document.readyState) && typeof(document.body) !== "undefined" && document.body != null) { 14510 f(); 14511 } 14512 else { 14513 setTimeout(_do, 9); 14514 } 14515 }; 14516 14517 _do(); 14518 }; 14519 ready(_jp.init); 14520 14521 }).call(typeof window !== 'undefined' ? window : this); 14522